5

我有一个Task我正在启动并希望在 WPF 应用程序中等待完成的程序。在这个任务中,我在调度程序上调用一个Action

如果我使用Task.Wait()它似乎挂起,好像该方法从未完成。此外,Dispatcher.Invoke 中的断点永远不会被命中。

如果我使用Task.RunSyncronously()它,它似乎工作正常,并且 Dispatcher 内的断点被命中。

为什么有区别?

下面的代码示例:

public void ExampleMethod()
{
    // When doing the following:
    var task = new Task(LoadStuff);

    // This never returns:
    task.Start();
    task.Wait();

    // This version, however, does:
    task.RunSyncronously();
}

private void LoadStuff()
{
    ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true);

    DispatchHelper.RunOnDispatcher(() =>
    {
        ...
    });
}

public static class DispatchHelper
{
    public static void RunOnDispatcher(Action action)
    {
        Application.Current.Dispatcher.Invoke(action);
    }
}    
4

1 回答 1

5

是的,有很大的不同。如果您使用RunSyncronously,您只需在 UI 线程中运行任务。如果您在后台线程中启动它,Wait然后我们在后台线程中运行代码,并且 UI 线程被阻塞。如果该任务中的代码正在调用 UI 线程,并且 UI 线程被阻塞(由Wait),那么您已经创建了死锁,并且应用程序将保持冻结状态。

请注意,如果您RunSyncronously在非 UI 线程的该任务上使用,并且 UI 线程被其他东西阻塞,您仍然会看到死锁。

现在,至于你应该做什么,这里真的有两个选择:

  1. 任务本身实际上并不需要很长时间,它确实应该在 UI 线程中而不是在后台线程中运行。UI 线程不会被冻结(暂时)足够长的时间,以至于直接在 UI 中完成所有这些工作是一个问题。如果是这种情况,您可能甚至不应该将其设为 Task,只需将代码放入方法中并调用该方法即可。

  2. 该任务确实需要很长时间才能运行,然后它会在完成该工作后更新 UI。如果是这种情况,那么重要的是不要RunSyncronously在后台线程中启动它。为了防止整个应用程序死锁,这意味着您不需要通过Wait调用阻塞 UI 线程。如果您有一些要在任务完成后运行的代码,您需要做的是向任务添加一个延续。在 C# 4.0 中,这可以通过调用ContinueWith任务并添加要运行的委托来完成。在 C# 5.0+ 中,您可以改为await执行相关任务(而不是Wait在它上面,这实际上是一个很大的区别),它会自动连接方法的其余部分作为你的延续(实际上它是显式ContinueWith调用的语法糖,但它是一个非常有用的)。

于 2012-11-01T20:48:08.577 回答