#c# #task-parallel-library
Вопрос:
Я преобразовал EAP в TAP. Однако событие, которое обрабатывается, может вообще не запускаться, поэтому я добавляю тайм-аут. См. Пример кода
class Request<T> {
TaskCompletionSource<T> tcs;
CancellationTokenSource cts;
public int Timeout { get; }
public bool IsCanceled => tcs.Task.IsCanceled;
public Request(int timeout = System.Threading.Timeout.Infinite) => Timeout = timeout;
public Task<T> StartRequestAsync() {
cts.Token.Register(() => tcs.SetCanceled());
cts.CancelAfter(Timeout);
return tcs.Task;
}
public void OnEvent(T result) {
tcs.SetResult(result);
}
}
Обратите внимание, что CancellationTokenSource
CancellationTokenRegistration
оба и расположены должным образом в реализации IDisposable
текущего класса. Этот StartRequestAsync
метод также поточно-безопасно предотвращается от запуска более одного раза. Код упрощен для краткости.
Теперь я внешне устанавливаю результат выполнения задачи.
var req = new Request<int>();
if(!req.IsCanceled)
{
req.SetResult(5);
}
Но я думаю, что есть расовое условие. Я предполагаю, что маркер отмены CancelAfter
работает как прерывание. Если токен будет отменен ровно после того, как выполнение войдет в if
блок, мы могли бы получить InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
В документации говорится, что TaskCompletionSource потокобезопасен, так значит ли это, что TrySetResult
это атомарная версия того, чего я пытаюсь достичь здесь? Это то, что я должен использовать вместо этого?
Есть ли какой-нибудь другой способ решить эту проблему?
Комментарии:
1. Да, TrySetResult является атомарным и потокобезопасным и будет делать то, что вы хотите.
2. @canton7 Спасибо
3. Тем не менее, вы, вероятно, тоже хотите, чтобы TrySetCanceled, по той же причине
Ответ №1:
Это может помочь увидеть, как SetResult
SetCanceled
реализуются методы и:
public void SetResult(TResult result)
{
if (!TrySetResult(result))
throw new InvalidOperationException(
Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted"));
}
public void SetCanceled()
{
if(!TrySetCanceled())
throw new InvalidOperationException(
Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted"));
}
Как вы можете видеть, оба метода делегируют полномочия своим Try
коллегам. Они нужны вам для обнаружения ошибок в вашем коде, если ваш сценарий детерминирован. Если это не так, и условия гонки заложены в сценарии, просто используйте Try
версии.