У меня есть следующий метод, который ожидает события, чтобы установить результаты с помощью TaskCompletionSource
.
private async Task<string?> RequestDataAsync(string? remoteEntity, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<string?>();
try
{
if (remoteEntity is not null)
{
var handler = GetHandler();
handler.Event.OnResultReady += result =>
{
taskCompletionSource.SetResult(result);
};
await SendRequestToAcquireDataAsync(cancellationToken);
}
else
{
taskCompletionSource.SetResult(null);
}
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
return await taskCompletionSource.Task;
}
Как указано в коде, мы подписываемся на событие OnResultReady
и после строки подписки отправляем запрос на удаленный сервер для получения данных. Сервер обычно достаточно быстр, чтобы предоставить результаты, но были случаи, когда сервер не реагировал достаточно быстро. Поэтому мы хотим завершить работу RequestDataAsync()
через 10 секунд, чтобы предотвратить блокировку клиентского приложения. Итак, я написал следующий код-оболочку для вызова метода RequestDataAsync()
с использованием токена отмены, который срабатывает через 10 секунд:
public async Task<string?> RequestDataWithTimeoutAsync(string? remoteEntity, CancellationToken cancellationToken, int requestLifetimeInMilliseconds = 10000)
{
var lifeTimeCancellationTokenSource = new CancellationTokenSource();
using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, lifeTimeCancellationTokenSource.Token);
lifeTimeCancellationTokenSource.CancelAfter(requestLifetimeInMilliseconds);
string? result = null;
try
{
result = await RequestDataAsync(remoteEntity, linkedCancellationToken.Token);
}
catch (OperationCanceledException oex) when (oex.CancellationToken == linkedCancellationToken.Token)
{
if (cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(oex.Message, oex, cancellationToken);
}
else if (lifeTimeCancellationTokenSource.IsCancellationRequested)
{
throw new TimeoutException();
}
}
return result;
}
SendRequestToAcquireDataAsync()
отправляет веб-запрос в веб-API, внутренний код которого создает данные асинхронно и делает их доступными через обработчик событий обработчика. Представьте себе это как надежную лазурную функцию.
Проблема, с которой я столкнулся, заключается в том, что метод-оболочка не завершает первый метод через 10 секунд, и первый метод зависает на неопределенный срок. Каково решение этой проблемы?
@Charlieface Общий код не требует пояснений.
@Arash Charlieface сделал правильный запрос, и я поддерживаю его. Правильно ли RequestDataAsync
использует CancellationToken
? Это наиболее вероятный виновник
Также не нужен lifeTimeCancellationTokenSource
. Сделайте using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); linkedCancellationTokenSource.CancelAfter(...);
@canton7 canton7 это не решает проблему.
Нет, это не так. Вот почему это комментарий.
Чтобы понять проблему, нам нужно увидеть RequestDataAsync
.
@canton7 Код RequestDataAsync
уже указан в вопросе как первый метод, верно? Ключевым моментом является то, что он ожидает возникновения события handler.Event.OnResultReady
. Это событие вызывается другим фрагментом кода, который получает событие от удаленного объекта, например триггер функции. Эта конкретная часть не имеет значения, поскольку речь идет о событиях. Но могут быть случаи, когда событие не возникает и, следовательно, обработчик событий не запускается.
Моя вина. Нам нужно посмотреть SendRequestToAcquireDataAsync
. Я не могу сказать, правильно ли оно прерывается, когда его CancellationToken
отменяют, используя только силу мозга.
Если SendRequestToAcquireDataAsync
завершится довольно быстро, тогда да, вам нужно будет зарегистрировать действие в CancellationToken`, которое отменит ваш TCS. Вероятно, вы также захотите отказаться от подписки на это событие.
@canton7 canton7 Я обновил вопрос. SendRequestToAcquireDataAsync()
отправляет веб-запрос в веб-API, внутренний код которого создает данные асинхронно и делает их доступными через обработчик событий обработчика. Представьте себе это как надежную лазурную функцию. Отписка не решает проблему. Моя проблема в том, что этот метод продолжает зависать, если удаленный объект не получает событие.
@ТеодорЗулиас .NET 8.0
Не уверен, насколько отрицательно проголосовали за этот вопрос?! Это правильный вопрос C#, показывающий проблему, которую необходимо решить. Нежелательно делать это без уважительной причины.
Посмотрите этот ответ. Существует встроенный метод WaitAsync
, который делает то, что вы хотите (отмените ожидание после таймаута и позвольте задаче перейти в режим «выпустил и забыл»). Нет необходимости изобретать его заново.
@TheodorZoulias Это тоже отличный ответ. Я попробую сегодня позже. Спасибо.
Если SendRequestToAcquireDataAsync
корректно отменяется, когда его CancellationToken
отменяется, то вы, вероятно, просто ждете, чтобы OnResultReady
уволили, даже после того, как ваш CancellationToken
был отменен.
Итак, вам, вероятно, потребуется зарегистрировать действие с помощью CancellationToken
, чтобы отменить TaskCompletionSource
в случае его отмены. Что-то вроде:
private async Task<string?> RequestDataAsync(string? remoteEntity, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<string?>();
try
{
if (remoteEntity is not null)
{
var handler = GetHandler();
handler.Event.OnResultReady += result =>
{
taskCompletionSource.TrySetResult(result);
};
await SendRequestToAcquireDataAsync(cancellationToken);
}
else
{
taskCompletionSource.SetResult(null);
}
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())
{
return await taskCompletionSource.Task;
}
}
Я также изменил SetResult
на TrySetResult
, чтобы избежать исключения, если событие будет запущено после отмены CancellationToken
.
Вероятно, вам также следует отказаться от подписки на это событие, когда все закончится:
private async Task<string?> RequestDataAsync(string? remoteEntity, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<string?>();
Handler? handler = null;
void ResultReadyHandler(string result)
{
taskCompletionSource.TrySetResult(result);
}
try
{
if (remoteEntity is not null)
{
handler = GetHandler();
handler.Event.OnResultReady += ResultReadyHandler;
await SendRequestToAcquireDataAsync(cancellationToken);
}
else
{
taskCompletionSource.SetResult(null);
}
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())
{
try
{
return await taskCompletionSource.Task;
}
finally
{
if (handler != null)
{
handler.Event.OnResultReady -= ResultReadyHandler;
}
}
}
}
(Очевидно, исправьте тип Handler
).
Тем не менее, вы делаете это более сложным, чем должно быть. В вашем catch
вы устанавливаете исключение для TaskCompletionSource
, а затем ожидаете TaskCompletionSource.Task
, который просто повторно генерирует исключение. Это просто многоречивый способ просто позволить исключению всплыть, что происходит автоматически. То же самое с возвратом null
:
private async Task<string?> RequestDataAsync(string? remoteEntity, CancellationToken cancellationToken)
{
if (remoteEntity is null)
{
return null;
}
var taskCompletionSource = new TaskCompletionSource<string?>();
void ResultReadyHandler(Result result)
{
taskCompletionSource.TrySetResult(result);
}
var handler = GetHandler();
handler.Event.OnResultReady += ResultReadyHandler;
try
{
await SendRequestToAcquireDataAsync(cancellationToken)
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())
{
return await taskCompletionSource.Task;
}
}
finally
{
handler.Event.OnResultReady -= ResultReadyHandler;
}
}
Тем не менее, почему у SendRequestToAcquireDataAsync
такой неуклюжий API? Почему он возвращает Task
, который нужно дождаться, а затем запускает событие, на которое нужно подписаться?
Спасибо @canton7, сегодня попробую чуть позже и буду держать вас в курсе.
потрясающее решение!! Спасибо!
Что на самом деле делает
RequestDataAsync
: вы полностью используете токен?