等待C#中的后台线程

时间:2014-09-18 11:27:41

标签: c# multithreading async-await

有什么区别:

public class Worker
{
    public static void Execute()
    {
        ExecuteInBackgroundThread().Wait();
    }

    private static Task ExecuteInBackgroundThread()
    {
        return Task.Run(() =>
        {
            // do some long running operation
        });
    }
}

public class Worker
{
    public static void Execute()
    {
        ExecuteInBackgroundThread().Wait();
    }

    private static async Task ExecuteInBackgroundThread()
    {
        await Task.Run(() =>
        {
            // do some long running operation
        });
    }
}

我注意到从我的Windows Phone应用程序的UI线程调用Worker.Execute()的第二个版本被卡住了。

相反,使用第一个版本似乎一切正常。

ExecuteInBackgroundThread的第二个版本实际上是否可以等待Task<T>?但如果情况并非如此,编译器是否应该提出错误说我们没有返回任何内容?

2 个答案:

答案 0 :(得分:5)

首先,你的应用程序卡住的真正原因是你有一个死锁。那是因为你正在使用Task.Wait阻塞正在等待UI线程完成的任务的UI线程。

这是因为只有一个UI线程,所以只有SynchronizationContext只使用该特定线程。

您可以使用不使用ConfigureAwait(false)的{​​{1}}解决僵局:

SynchronizationContext

除此之外,两个选项几乎相同,只要private static async Task<T> ExecuteInBackgroundThread<T>() { await Task.Run(() => { // do some long running operation }).ConfigureAwait(false); } 中没有更多代码在ExecuteInBackgroundThread的调用之外。第一个选项稍快,因为您直接使用Task.Run中的任务。第二个选项在顶部添加另一个冗余异步层(包括状态机等)。

结论:您应该使用第一个选项。但是不要使用Task.Run,它不仅会阻塞你的线程,而且在某些情况下可能会导致死锁。

答案 1 :(得分:2)

让我们看看每个版本调用Execute的内容:

第一个版本调用ExecuteInBackgroundThread,它会立即返回Task。然后Execute阻塞,直到返回的任务完成。

第二个版本调用ExecuteInBackgroundThread创建一个Task并安排一个延续,当任务完成时将执行该延续,并返回另一个任务。然后Execute阻塞,直到返回的任务完成。

不同之处在于,由于await关键字,第二个版本具有连续调度。它的效果取决于您调用Execute的线程的同步上下文。当您在UI线程上时,第二个版本将死锁,因为延迟是在UI线程上安排的,该线程因调用Wait而被阻止。

如果您正在使用控制台应用程序,则不会出现死锁,因为默认同步上下文使用线程池来执行延续。