我们最近采用了TPL作为运行一些繁重后台任务的工具包。
这些任务通常生成一个实现IDisposable
的对象。这是因为它内部有一些操作系统句柄。
我希望的是,后台线程生成的对象将始终得到正确的处理,即使在切换与应用程序关闭同时进行时也是如此。
经过一番思考,我写下了这篇文章:
private void RunOnUiThread(Object data, Action<Object> action)
{
var t = Task.Factory.StartNew(action, data, CancellationToken.None, TaskCreationOptions.None, _uiThreadScheduler);
t.ContinueWith(delegate(Task task)
{
if (!task.IsCompleted)
{
DisposableObject.DisposeObject(task.AsyncState);
}
});
}
后台Task
调用RunOnUiThread
将其结果传递给UI线程。任务t
是在UI线程上调度的,并拥有传入的data
的所有权。我原本预计,如果由于UI线程的消息泵关闭而导致t
无法执行,则会运行continuation,我可以看到任务失败,并自己处理对象。DisposeObject()
是一个助手,在处理对象之前,它会检查对象是否真的是IDisposable并且不是null
遗憾的是,它不起作用。如果在创建后台任务t
之后关闭应用程序,则不执行继续操作。
我以前解决过这个问题。当时我使用Threadpool和WPF Dispatcher在UI线程上发布消息。虽然不是很漂亮,但最终还是成功了。我希望TPL在这种情况下做得更好。如果我能以某种方式教TPL,如果它们实现IDisposable,它应该处理所有剩余的AsyncState对象,那会更好。
所以,代码主要是为了说明这个问题。我想了解任何允许我从后台任务安全地将一次性对象移交给UI线程的解决方案,最好是一个代码尽可能少的解决方案。
当进程关闭时,它的所有内核句柄都会自动关闭。你不需要担心这个:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686722(v=vs.85).aspx
查看RX库。这可以让你做你想做的事。
来自MSDN:
当Task处于以下三种情况之一时,
IsCompleted
将返回true最终状态:RanToCompletion
、Faulted
或Canceled
换言之,您的DisposableObject.DisposeObject
永远不会被调用,因为延续将始终安排在上述条件之一发生之后。我相信你的意思是:
t.ContinueWith(t => DisposableObject.DisposeObject(task.AsyncState),
TaskContinuationOptions.NotOnRanToCompletion)
(顺便说一句,您可以简单地捕获data
变量,而不是使用AsyncState
属性)
然而,对于您希望确保始终发生的事情,我不会使用延续。我相信try-finally
块在这里会更合适:
private void RunOnUiThread2(Object data, Action<Object> action)
{
var t = Task.Factory.StartNew(() =>
{
try
{
action(data);
}
finally
{
DisposableObject.DisposeObject(task.AsyncState);
//Or use a new *foreground* thread if the disposing is heavy
}
}, CancellationToken.None, TaskCreationOptions.None, _uiThreadScheduler);
}