使用 async/await 时出现死锁
Deadlock while using async/await
我正在尝试理解 await
和 async
。
效果很好。但是现在我陷入了僵局。
我用 false
调用了 ConfigureAwait
,就像在 this article 中一样,但我的代码仍然阻塞。
这是我的一小段代码:
private void button1_Click(object sender, EventArgs e)
{
var result = HeavyWorkAsync().Result;
richTextBox1.AppendText(result);
}
private string HeavyWork()
{
for (var index = 0; index < 1000; index++)
{
Task.Delay(10).Wait();
}
return "finished";
}
private async Task<string> HeavyWorkAsync()
{
var task = await Task.Factory.StartNew<string>(HeavyWork).ConfigureAwait(false);
return task;
}
阻塞的不是任务本身,而是对 Result
的调用。一个Task
表示一个异步操作,但是调用它的Result
属性,或者调用Wait()
会阻塞当前线程直到方法returns。而且在很多情况下,它会导致死锁,因为任务无法完成,调用线程被阻塞!
为防止这种情况,使用 async
和 await
异步链接任务
private async void button1_Click(object sender, EventArgs e)
{
var result = await HeavyWorkAsync(); // <=== await
richTextBox1.AppendText(result);
}
此外,Task.Delay(10).Wait();
完全违背了使用任务的初衷:这将阻塞当前线程。如果那是真的你想做的(而且这不太可能),请改为调用Thread.Sleep(10);
,它会让你的意图更加清晰,你将有更少的障碍可以跳过.或者更好的是,在异步方法中使用 await Task.Delay(10);
。
关于ConfigureAwait
ConfigureAwait(false)
到底是做什么的?
它消除了在与任务调用者相同的上下文中将任务继续到 运行 的义务。在大多数情况下,这意味着不再保证在同一上下文中继续 运行。所以如果我有一个方法 thad Foo()
,稍等一下然后 Bar()
就像这个:
async Task DoStufAsync()
{
Foo();
await Task.Delay(10);
Bar(); // run in the same context as Foo()
}
我保证 Bar 会 运行 在相同的上下文中。如果我有ConfigureAwait(false)
,现在就不是
async Task DoStufAsync()
{
Foo();
await Task.Delay(10).ConfigureAwait(false);
Bar(); // can run on another thread as Foo()
}
当您使用 ConfigureAwait(false)
时,您告诉您的程序您不介意上下文。它可以解决一些死锁问题,但通常不是正确的解决方案。正确的解决方案很可能永远不会以阻塞的方式等待任务,并且一直异步。
要扩展 Falanwe 的答案,您应该查看 Stephen Cleary's blog post。根据代码,我假设您正在使用 Windows Forms 应用程序,因此对 Task.Result 的调用将在 UI 上下文中执行任务,这反过来会阻止 UI线程。
我正在尝试理解 await
和 async
。
效果很好。但是现在我陷入了僵局。
我用 false
调用了 ConfigureAwait
,就像在 this article 中一样,但我的代码仍然阻塞。
这是我的一小段代码:
private void button1_Click(object sender, EventArgs e)
{
var result = HeavyWorkAsync().Result;
richTextBox1.AppendText(result);
}
private string HeavyWork()
{
for (var index = 0; index < 1000; index++)
{
Task.Delay(10).Wait();
}
return "finished";
}
private async Task<string> HeavyWorkAsync()
{
var task = await Task.Factory.StartNew<string>(HeavyWork).ConfigureAwait(false);
return task;
}
阻塞的不是任务本身,而是对 Result
的调用。一个Task
表示一个异步操作,但是调用它的Result
属性,或者调用Wait()
会阻塞当前线程直到方法returns。而且在很多情况下,它会导致死锁,因为任务无法完成,调用线程被阻塞!
为防止这种情况,使用 async
和 await
private async void button1_Click(object sender, EventArgs e)
{
var result = await HeavyWorkAsync(); // <=== await
richTextBox1.AppendText(result);
}
此外,Task.Delay(10).Wait();
完全违背了使用任务的初衷:这将阻塞当前线程。如果那是真的你想做的(而且这不太可能),请改为调用Thread.Sleep(10);
,它会让你的意图更加清晰,你将有更少的障碍可以跳过.或者更好的是,在异步方法中使用 await Task.Delay(10);
。
关于ConfigureAwait
ConfigureAwait(false)
到底是做什么的?
它消除了在与任务调用者相同的上下文中将任务继续到 运行 的义务。在大多数情况下,这意味着不再保证在同一上下文中继续 运行。所以如果我有一个方法 thad Foo()
,稍等一下然后 Bar()
就像这个:
async Task DoStufAsync()
{
Foo();
await Task.Delay(10);
Bar(); // run in the same context as Foo()
}
我保证 Bar 会 运行 在相同的上下文中。如果我有ConfigureAwait(false)
,现在就不是
async Task DoStufAsync()
{
Foo();
await Task.Delay(10).ConfigureAwait(false);
Bar(); // can run on another thread as Foo()
}
当您使用 ConfigureAwait(false)
时,您告诉您的程序您不介意上下文。它可以解决一些死锁问题,但通常不是正确的解决方案。正确的解决方案很可能永远不会以阻塞的方式等待任务,并且一直异步。
要扩展 Falanwe 的答案,您应该查看 Stephen Cleary's blog post。根据代码,我假设您正在使用 Windows Forms 应用程序,因此对 Task.Result 的调用将在 UI 上下文中执行任务,这反过来会阻止 UI线程。