使用TPL将“一次性”对象安全地传递给UI线程



我们最近采用了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最终状态: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);
}

最新更新