等待任务取消永远等待
Wait for Task to cancel waits forever
我有一个表单,我在其中启动了一个任务来加载内容。如果用户单击取消,则此任务当然需要取消。但似乎我做错了什么。表单永远不会关闭并一直等待任务:
public partial class Designer : Form
{
private CancellationTokenSource _cancellationTokenSource;
private Task _loadTask;
private async void Designer_Shown(object sender, EventArgs e)
{
_cancellationTokenSource = new CancellationTokenSource();
try
{
_loadTask= Workbench.Instance.CurrentPackage.LoadObjects(_cancellationTokenSource.Token);
await _loadTask;
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
_loadTask.Wait(); //Waits forever
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
我错在哪里?
编辑
LoadObjects()
的代码
public Task LoadObjects(CancellationToken cancelToken)
{
return Task.Run(() =>
{
LoadParameters(cancelToken);
LoadConditionChecks(cancelToken);
LoadConditonRules(cancelToken);
LoadOperations(cancelToken);
}, cancelToken);
}
我将令牌传递给子方法,因为循环确实存在...
您在等待 UI 线程并调用 Task.Wait()
时遇到了死锁。不惜一切代价避免Task.Wait
。
在异步延续中为取消结果提供服务,如下所示:
private async void btnCancel_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
await _loadTask;
this.DialogResult = DialogResult.Cancel;
this.Close();
}
这是唯一一次 async void
可以接受。
我最喜欢的异步代码专家 Stephen Cleary 提供了一篇精彩的博客 post 解释了为什么您应该避免将 Task.Wait
和 Task.Result
作为阻塞机制 - Don't Block on Async Code
无论如何,当我取消时,我从不等待任务完成。我立即响应取消并让任务在后台完成。这为用户提供了响应式 UI 体验。如果我需要从已取消的任务中获得结果,我会在用户单击取消后投入一些 UI 工作来沟通 'waiting for end of operation'。
您没有使用取消令牌。这种模式被称为协作任务取消——消费者和执行代码都需要为取消服务activity。这有助于您在取消后保持正确的状态。
解决方案 - 更新您的 LoadX() 方法:
void LoadParameters(cancelToken)
{
... // do some work
cancelToken.ThrowIfCancellationRequested();
... // do some more work
}
我有一个表单,我在其中启动了一个任务来加载内容。如果用户单击取消,则此任务当然需要取消。但似乎我做错了什么。表单永远不会关闭并一直等待任务:
public partial class Designer : Form
{
private CancellationTokenSource _cancellationTokenSource;
private Task _loadTask;
private async void Designer_Shown(object sender, EventArgs e)
{
_cancellationTokenSource = new CancellationTokenSource();
try
{
_loadTask= Workbench.Instance.CurrentPackage.LoadObjects(_cancellationTokenSource.Token);
await _loadTask;
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
_loadTask.Wait(); //Waits forever
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
我错在哪里?
编辑
LoadObjects()
public Task LoadObjects(CancellationToken cancelToken)
{
return Task.Run(() =>
{
LoadParameters(cancelToken);
LoadConditionChecks(cancelToken);
LoadConditonRules(cancelToken);
LoadOperations(cancelToken);
}, cancelToken);
}
我将令牌传递给子方法,因为循环确实存在...
您在等待 UI 线程并调用 Task.Wait()
时遇到了死锁。不惜一切代价避免Task.Wait
。
在异步延续中为取消结果提供服务,如下所示:
private async void btnCancel_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
await _loadTask;
this.DialogResult = DialogResult.Cancel;
this.Close();
}
这是唯一一次 async void
可以接受。
我最喜欢的异步代码专家 Stephen Cleary 提供了一篇精彩的博客 post 解释了为什么您应该避免将 Task.Wait
和 Task.Result
作为阻塞机制 - Don't Block on Async Code
无论如何,当我取消时,我从不等待任务完成。我立即响应取消并让任务在后台完成。这为用户提供了响应式 UI 体验。如果我需要从已取消的任务中获得结果,我会在用户单击取消后投入一些 UI 工作来沟通 'waiting for end of operation'。
您没有使用取消令牌。这种模式被称为协作任务取消——消费者和执行代码都需要为取消服务activity。这有助于您在取消后保持正确的状态。
解决方案 - 更新您的 LoadX() 方法:
void LoadParameters(cancelToken)
{
... // do some work
cancelToken.ThrowIfCancellationRequested();
... // do some more work
}