C# 正确取消和清除CancellationToken
我们有一个名为C# 正确取消和清除CancellationToken,c#,asynchronous,async-await,cancellationtokensource,C#,Asynchronous,Async Await,Cancellationtokensource,我们有一个名为GetThings的async方法,该方法将发送到4个不同的提供者并查找结果。其中两个提供程序速度非常快,因此不是异步写入的,但是其他两个提供程序速度较慢,并且是异步写入的 对文本更改调用GetThings方法,因此对它的调用应取消以前的调用。我们使用CancellationTokenSource来实现这一点。GetThings方法的开头如下所示: private async void GetThings() { if (this.quickEntryCancellatio
GetThings
的async
方法,该方法将发送到4个不同的提供者并查找结果。其中两个提供程序速度非常快,因此不是异步写入的,但是其他两个提供程序速度较慢,并且是异步写入的
对文本更改调用GetThings方法,因此对它的调用应取消以前的调用。我们使用CancellationTokenSource来实现这一点。GetThings方法的开头如下所示:
private async void GetThings()
{
if (this.quickEntryCancellationTokenSource != null &&
this.quickEntryCancellationTokenSource.IsCancellationRequested == false)
{
this.quickEntryCancellationTokenSource.Cancel();
}
this.quickEntryCancellationTokenSource = new CancellationTokenSource();
我们决定在所有4个提供者都完成之前,从GetThings返回任何内容都没有意义。我们正在完成这项任务
var getThings1Task = this.GetThings1();
var getThings2Task = this.GetThings2();
var getThings3Task = this.GetThings3();
var getThings4Task = this.GetThings4();
await Task.WhenAll(getThings1Task, getThings2Task, getThings3Task, getThings4Task).ConfigureAwait(false);
// Read the .Result of each of the tasks
localSuggestions.Insert(0, getThings1Tasks.Result);
localSuggestions.Insert(1, getThings2Tasks.Result);
localSuggestions.Insert(2, getThings3Tasks.Result);
localSuggestions.Insert(3, getThings4Tasks.Result);
this.quickEntryCancellationTokenSource = null;
读取每个任务的.Result后,我们将quickEntryCancellationTokenSource设置为null。所有这些代码都包装在通常的CancellationTokenSource异常处理中。必要时,GetThings3的开始将CancellationTokenSource向下传递一层
private async Task<GroupedResult<string, SearchSuggestionItemViewModel>> GetThings3()
{
List<Code> suggestions;
if (this.SearchTerm.Length >= 3)
{
suggestions = await this.provider3.SearchAsync(this.SearchTerm, this.quickEntryCancellationTokenSource.Token);
}
else
{
suggestions = new List<Code>();
}
我们看到的问题是,有时quickEntryCancellationTokenSource为null。在我的天真中,我看不出这是怎么可能的,因为CancellationTokenSource是在等待任务之前创建的,并且没有设置为Null
直到他们完成
我的问题是:
quickEntryCancellationTokenSource
字段
如果这是在UI线程的上下文中,则可以使用UI线程进行同步:
// No ConfigureAwait(false); we want to only access the CTS from the UI thread.
await Task.WhenAll(getThings1Task, getThings2Task, getThings3Task, getThings4Task);
...
this.quickEntryCancellationTokenSource = null;
也可以考虑将CT传递给<代码> GETTHECGs1Tase和其他,而不是让它们从私有变量读取。
这四个方法是否都应该调用ThrowIfCancellationRequested 这取决于你。如果您的两个“快速”提供者方法不进行CT检查,那么您可以检查它,但在我看来它不会提供任何好处 四个提供者方法中的两个从不等待任何东西,这是错误的吗 如果他们在做I/O,他们应该使用wait
,即使他们通常非常快。如果他们没有做I/O,那么我看不出将他们包装在任务中有什么意义。据推测,当前代码是同步执行的,并且总是返回一个已完成的任务,因此这只是同步执行代码的一种更复杂的方式。OTOH,如果他们正在执行I/O,但您没有异步API(并且由于您处于UI上下文中),则可以将它们包装到任务中。运行
一旦我们阅读了每个任务的结果
考虑使用wait
,即使对于已完成的任务也是如此。这将使异常处理代码更容易
我们是否应该同步访问cancellationTokenSource?因为这四个提供者中有两个速度很快,所以他们从不处理cancellationTokenSource
您应该同步对CTS的访问,但不是出于这个原因。原因是可以从多个线程访问quickEntryCancellationTokenSource
字段
如果这是在UI线程的上下文中,则可以使用UI线程进行同步:
// No ConfigureAwait(false); we want to only access the CTS from the UI thread.
await Task.WhenAll(getThings1Task, getThings2Task, getThings3Task, getThings4Task);
...
this.quickEntryCancellationTokenSource = null;
也可以考虑将CT传递给<代码> GETTHECGs1Tase和其他,而不是让它们从私有变量读取。
这四个方法是否都应该调用ThrowIfCancellationRequested
这取决于你。如果您的两个“快速”提供者方法不进行CT检查,那么您可以检查它,但在我看来它不会提供任何好处
四个提供者方法中的两个从不等待任何东西,这是错误的吗
如果他们在做I/O,他们应该使用wait
,即使他们通常非常快。如果他们没有做I/O,那么我看不出将他们包装在任务中有什么意义。据推测,当前代码是同步执行的,并且总是返回一个已完成的任务,因此这只是同步执行代码的一种更复杂的方式。OTOH,如果他们正在执行I/O,但您没有异步API(并且由于您处于UI上下文中),则可以将它们包装到任务中。运行
一旦我们阅读了每个任务的结果
考虑使用wait
,即使对于已完成的任务也是如此。这将使您的异常处理代码更容易。我对您的问题的回答是:
否。CancellationTokenSource
,但对字段本身的访问应该同步。我有两个建议来避免这个问题:
首先,最好直接将CancellationToken
传递给GetThings3
,而不是引用全局变量:
私有异步任务GetThings3(CancellationToken令牌){…}
var getThings3Task=this.GetThings3(quickEntryCancellationTokenSource.Token);
其次,最好避免访问外部变量。我的建议是使用本地CancellationTokenSource
:
使用(var localTokenSource=new CancellationTokenSource())
{
var getThings1Task=this.GetThings1(localTokenSource.Token);
...
等待任务;
}
如果要创建与全局令牌源链接的令牌,您可能还需要考虑CancellationTokenSource.CreateLinkedTokenSource(…)
是的,最好跟着做