c # сделать функцию синхронизации ожидаемой и отменяемой

Мне нужно вызвать какую-то функцию синхронизации, и мне нужно, чтобы они работали в фоновом режиме и могли быть отменены. Итак, я пишу это:

private static async Task WaitSyncFunction(Action syncFunction,
    int timeoutMilliseconds,
    CancellationToken token)
{
    var syncFunctionTask = Task.Run(syncFunction);
    var timeoutTask = Task.Delay(timeoutMilliseconds, token);
    await Task.WhenAny(timeoutTask, syncFunctionTask).ContinueWith(task =>
    {
        if (timeoutTask.IsCanceled) throw new TaskCanceledException();
        if (timeoutTask.IsCompletedSuccessfully) throw new TimeoutException();
        if (syncFunctionTask.IsFaulted) throw syncFunctionTask.Exception.InnerException;
    });
}

используй это:

//...async method of button click event handler in window
cancelSource = new CancellationTokenSource();
try
{
    await WaitSyncFunction(() => MySyncFunction(), 5000, cancelSource.Token);
    MessageBox.Show("Success!");
}
catch (TaskCanceledException) { MessageBox.Show("Canceled!"); }
catch (Exception ex) { MessageBox.Show("Error! "+ ex.Message); }
//...

Какие риски могут существовать в кодах, и противоречит ли это шаблону?

============ Редактировать ============
Некоторые улучшения:

//Custom exception for process task after timeout
public class TaskTimeoutException : TimeoutException
{
    public Task task { get; }
    public TaskTimeoutException(Task task)
        => this.task = task;
}

private static async Task WaitSyncFunction(Action syncFunction,
    int timeoutMilliseconds,
    CancellationToken token)
{
    var syncFunctionTask = Task.Run(syncFunction);
    var timeoutTask = Task.Delay(timeoutMilliseconds, token);
    await Task.WhenAny(timeoutTask, syncFunctionTask);//.ContinueWith(task =>
    //{ //Unnecessary ContinueWith
    //if (timeoutTask.IsCanceled) throw new TaskCanceledException();
    //return the function task so that it can do something else after it ending
    if (timeoutTask.IsCanceled) throw new TaskCanceledException(syncFunctionTask);
    //if (timeoutTask.IsCompletedSuccessfully) throw new TimeoutException();
    //return the function task so that it can do something else after it ending
    if (timeoutTask.IsCompletedSuccessfully) throw new TaskTimeoutException(syncFunctionTask);
    if (syncFunctionTask.IsFaulted) throw syncFunctionTask.Exception.InnerException;
    //});
}

1 ответ
1

Всякий раз, когда у нас есть асинхронный метод и мы хотим различать Canceled из Timeout тогда мы обычно делаем следующее:

  1. Мы ожидаем OperationCanceledException (базовый класс TaskCanceledException)
  2. Мы исследуем IsCancellationRequested собственность CancellationToken

Позвольте мне показать вам простой пример:

Timeout

private static readonly TimeSpan OperationDuration = TimeSpan.FromSeconds(3);
// timeoutSource will be triggered
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); 
private static readonly TimeSpan CancelAfter = TimeSpan.FromSeconds(10);

static async Task Main(string[] args)
{
    var userCancellationSource = new CancellationTokenSource(CancelAfter);
    try
    {
        await TestAsync(userCancellationSource.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine(userCancellationSource.IsCancellationRequested ? "Canceled" : "Timed out");
    }
}

public static async Task TestAsync(CancellationToken token = default)
{
    var timeoutSource = new CancellationTokenSource(Timeout);
    var timeoutOrCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, token);
    await Task.Delay(OperationDuration, timeoutOrCancellationSource.Token);
}

Canceled

private static readonly TimeSpan OperationDuration = TimeSpan.FromSeconds(3);
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); 
// userCancellationSource will be triggered 
private static readonly TimeSpan CancelAfter = TimeSpan.FromSeconds(1); 

static async Task Main(string[] args)
{
    var userCancellationSource = new CancellationTokenSource(CancelAfter);
    try
    {
        await TestAsync(userCancellationSource.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine(userCancellationSource.IsCancellationRequested ? "Canceled" : "Timed out");
    }
}

public static async Task TestAsync(CancellationToken token = default)
{
    var timeoutSource = new CancellationTokenSource(Timeout);
    var timeoutOrCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, token);
    await Task.Delay(OperationDuration, timeoutOrCancellationSource.Token);
}

Думаю, такому же шаблону должна следовать и ваша обертка. Чтобы иметь такое же поведение, я изменил ваш WaitSyncFunction метод следующим образом

private static async Task WaitSyncFunction(Action syncFunction, int timeoutMilliseconds, CancellationToken token)
{
    var syncFunctionTask = Task.Run(syncFunction);
    var timeoutTask = Task.Delay(timeoutMilliseconds, token);
    await Task.WhenAny(timeoutTask, syncFunctionTask);
    if (timeoutTask.IsCanceled) token.ThrowIfCancellationRequested(); //changed
    if (timeoutTask.IsCompletedSuccessfully) throw new OperationCanceledException(token); //changed
    if (syncFunctionTask.IsFaulted) throw syncFunctionTask.Exception.InnerException;
}

Timeout

private static readonly TimeSpan OperationDuration = TimeSpan.FromSeconds(3);
// timeoutSource will be triggered
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private static readonly TimeSpan CancelAfter = TimeSpan.FromSeconds(10);

static async Task Main(string[] args)
{
    var userCancellationSource = new CancellationTokenSource(CancelAfter);
    try
    {
        await WaitSyncFunction(() => Thread.Sleep((int) OperationDuration.TotalMilliseconds),
            (int) Timeout.TotalMilliseconds, userCancellationSource.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine(userCancellationSource.IsCancellationRequested ? "Canceled" : "Timed out");
    }
}

private static async Task WaitSyncFunction(Action syncFunction, int timeoutMilliseconds,
    CancellationToken token)
{
    var syncFunctionTask = Task.Run(syncFunction);
    var timeoutTask = Task.Delay(timeoutMilliseconds, token);
    await Task.WhenAny(timeoutTask, syncFunctionTask);
    if (timeoutTask.IsCanceled) token.ThrowIfCancellationRequested();
    if (timeoutTask.IsCompletedSuccessfully) throw new OperationCanceledException(token);
    if (syncFunctionTask.IsFaulted) throw syncFunctionTask.Exception.InnerException;
}

Canceled

private static readonly TimeSpan OperationDuration = TimeSpan.FromSeconds(3);
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
// userCancellationSource will be triggered 
private static readonly TimeSpan CancelAfter = TimeSpan.FromSeconds(1);

static async Task Main(string[] args)
{
    var userCancellationSource = new CancellationTokenSource(CancelAfter);
    try
    {
        await WaitSyncFunction(() => Thread.Sleep((int) OperationDuration.TotalMilliseconds),
            (int) Timeout.TotalMilliseconds, userCancellationSource.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine(userCancellationSource.IsCancellationRequested ? "Canceled" : "Timed out");
    }
}

private static async Task WaitSyncFunction(Action syncFunction, int timeoutMilliseconds,
    CancellationToken token)
{
    var syncFunctionTask = Task.Run(syncFunction);
    var timeoutTask = Task.Delay(timeoutMilliseconds, token);
    await Task.WhenAny(timeoutTask, syncFunctionTask);
    if (timeoutTask.IsCanceled) token.ThrowIfCancellationRequested();
    if (timeoutTask.IsCompletedSuccessfully) throw new OperationCanceledException(token);
    if (syncFunctionTask.IsFaulted) throw syncFunctionTask.Exception.InnerException;
}

Имейте в виду, что в этой реализации syncFunction не прерывается в случае тайм-аута. Чтобы поддержать совместную отмену, вам необходимо пройти CancellationToken к syncFunction и изучить его IsCancellationRequested недвижимость периодически (на каждом КПП / вехе).

Ознакомьтесь с ними для получения дополнительной информации:

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *