使用TPL将`Disposable`对象安全地传递给UI线程

时间:2012-02-10 22:38:02

标签: c#-4.0 task-parallel-library idisposable ui-thread

我们最近采用了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 ,继续运行,我可以看到任务失败了,并自己处理对象。 DisposeObject()是一个帮助程序,在处理它之前检查对象是否实际上是IDisposable,并且是非null。

可悲的是,它不起作用。如果在创建后台任务t后关闭应用程序,则不会执行继续。

我之前解决了这个问题。那时我使用Threadpool和WPF Dispatcher在UI线程上发布消息。它不是很漂亮,但最终它起作用了。我希望TPL在这种情况下更好。如果我能以某种方式告诉TPL它应该处理所有剩余的AsyncState对象(如果它们实现IDisposable),那会更好。

所以,代码主要是为了说明问题。我想了解任何允许我从后台任务安全地将Disposable对象切换到UI线程的解决方案,最好是尽可能少的代码。

3 个答案:

答案 0 :(得分:1)

当进程关闭时,它的所有内核句柄都会自动关闭。你不应该担心这个:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686722(v=vs.85).aspx

答案 1 :(得分:0)

看看RX库。这可以让你做你想做的事。

答案 2 :(得分:0)

来自MSDN

  当任务处于三者之一时,

IsCompleted将返回true   最终状态:RanToCompletionFaultedCanceled

换句话说,永远不会调用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);
}