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如何为空
  • 我们是否应该同步访问cancellationTokenSource? 因为四家供应商中有两家速度很快,他们从不与客户打交道 cancellationTokenSource
  • 这四个方法是否都应该调用ThrowIfCancellationRequested
  • 四个提供者方法中的两个从不等待任何东西,这是错误的吗
  • 我们是否应该同步访问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?因为这四个提供者中有两个速度很快,所以他们从不处理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(…)

  • 是的,最好跟着做