Мне нужно вызвать какую-то функцию синхронизации, и мне нужно, чтобы они работали в фоновом режиме и могли быть отменены. Итак, я пишу это:
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 ответ
Всякий раз, когда у нас есть асинхронный метод и мы хотим различать Canceled
из Timeout
тогда мы обычно делаем следующее:
- Мы ожидаем
OperationCanceledException
(базовый классTaskCanceledException
) - Мы исследуем
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
недвижимость периодически (на каждом КПП / вехе).
Ознакомьтесь с ними для получения дополнительной информации:
- Отмена в управляемых потоках
- Тема CodeReview: Обучение асинхронным задачам C # (пошаговое моделирование)