处理多个带有超时的CancellationToken

Spa*_*man 1 c# cancellation async-await cancellationtokensource cancellation-token

对于在以下情况下如何实现取消标记,我有些困惑。

说我有一个方法,它有一个取消令牌,没有指定超时,像这样。

public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
    try
    {
        Task.Delay(1000, cancellationToken)
    }
    catch (OperationCanceledException canceledException)
    {
        // Do something with canceledException
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
        throw;
    }
    catch (Exception exception)
    {
        // Do something with exception
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是在该方法中,我想调用另一个期望CancellationToken除此以外的方法,但这次我要对此设置超时。

public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
    try
    {
        var innerCancellationTokenSource = new CancellationTokenSource();
        innerCancellationTokenSource.CancelAfter(1000);
        var innerCancellationToken = innerCancellationTokenSource.Token;

        await DoSomeElseAsyncThingAsync(innerCancellationToken);
    }
    catch (OperationCanceledException canceledException)
    {
        // Do something with canceledException
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
        throw;
    }
    catch (Exception exception)
    {
        // Do something with exception
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何innerCancellationTokencancellationToken参数中获得取消请求的尊重?

我能想到的最好的是这样的:

public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
    try
    {
        await Task.WhenAny(
            DoSomeElseAsyncThingAsync(cancellationToken),
            KaboomAsync(100, cancellationToken)
        );
    }
    catch (OperationCanceledException canceledException)
    {
        // Do something with canceledException
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
        throw;
    }
    catch (Exception exception)
    {
        // Do something with exception
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
        throw;
    }
}

public static async Task KaboomAsync(int delay, CancellationToken cancellationToken = default)
{
    await Task.Delay(delay, cancellationToken);
    throw new OperationCanceledException();
}
Run Code Online (Sandbox Code Playgroud)

但是,这并不完全正确。该KaboomAsync()功能将始终崩溃,而且这条路似乎很粗糙。有更好的模式吗?


发布答案我创建了此静态Util方法,以节省我将样板放入一百万次的麻烦。

希望它对某人有用。

public static async Task<T> CancellableUnitOfWorkHelper<T>(
    Func<CancellationToken, Task<T>> unitOfWordFunc,
    int timeOut,
    CancellationToken cancellationToken = default
)
{
    try
    {
        var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
        using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
            return await unitOfWordFunc(linkedTokenSource.Token);
    }
    catch (OperationCanceledException canceledException)
    {
        Console.WriteLine(
            cancellationToken.IsCancellationRequested
                ? "Manual or parent Timeout {0}"
                : "UnitOfWork Timeout {0}"
            , canceledException
        );

        throw;
    }
    catch (Exception exception)
    {
        Console.WriteLine("Exception {0}", exception);
        throw;
    }
}

public static async Task CancellableUnitOfWorkHelper(
    Func<CancellationToken, Task> unitOfWordFunc,
    int timeOut,
    CancellationToken cancellationToken = default
)
{
    try
    {
        var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
        using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
            await unitOfWordFunc(linkedTokenSource.Token);
    }
    catch (OperationCanceledException canceledException)
    {
        Console.WriteLine(
            cancellationToken.IsCancellationRequested
                ? "Manual or parent Timeout {0}"
                : "UnitOfWork Timeout {0}"
            , canceledException
        );

        throw;
    }
    catch (Exception exception)
    {
        Console.WriteLine("Exception {0}", exception);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

它们可以像这样使用。

await Util.CancellableUnitOfWorkHelper(
   token => Task.Delay(1000, token),
   200
);
Run Code Online (Sandbox Code Playgroud)

要么

await Util.CancellableUnitOfWorkHelper(
   token => Task.Delay(1000, token),
   200,
   someExistingToken
);
Run Code Online (Sandbox Code Playgroud)

在这两个示例中,它将在200毫秒后超时,但第二个示例还将遵循“ someExistingToken”令牌的手动取消或超时。

Pet*_*iho 5

CancellationTokenSource 有专门针对这种情况的方法: CreateLinkedTokenSource

在您的示例中,它可能看起来像这样:

public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
    try
    {
        var innerCancellationTokenSource = new CancellationTokenSource();

        using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
        {
            innerCancellationTokenSource.CancelAfter(1000);

            await DoSomeElseAsyncThingAsync(linkedTokenSource.Token);
        }
    }
    catch (OperationCanceledException canceledException)
    {
        // Do something with canceledException
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
        throw;
    }
    catch (Exception exception)
    {
        // Do something with exception
        Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,布置链接源很重要,否则来自父令牌源的引用将防止其被垃圾回收。

另请参见区分取消和超时以及何时处置CancellationTokenSource的任何方法