TaskCompletionSource throws "Была сделана попытка перевести задачу в конечное состояние, когда она уже выполнена"
Я хочу использовать TaskCompletionSource
обернуть MyService
это простой сервис:
public static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
//Every time ProccessAsync is called this assigns to Completed!
service.Completed += (sender, e)=>{ tcs.SetResult(e.Result); };
service.RunAsync(parameter);
return tcs.Task;
}
Этот код работает хорошо в первый раз. Но второй раз звоню ProcessAsync
просто обработчик события для Completed
это назначить снова (то же самое service
переменная используется каждый раз) и, таким образом, она будет выполняться дважды! и второй раз выдает это исключение:
попытка перехода к конечному состоянию задачи, когда оно уже выполнено
Я не уверен, должен ли я объявить tcs
как переменная уровня класса, как это:
TaskCompletionSource<string> tcs;
public static Task<string> ProccessAsync(MyService service, int parameter)
{
tcs = new TaskCompletionSource<string>();
service.Completed -= completedHandler;
service.Completed += completedHandler;
return tcs.Task;
}
private void completedHandler(object sender, CustomEventArg e)
{
tcs.SetResult(e.Result);
}
Я должен обернуть многие методы с разными типами возвращаемых данных, и таким образом я должен написать потерянный код, переменные, обработчики событий, поэтому я не уверен, является ли это наилучшей практикой в этих сценариях. Так есть ли лучший способ сделать эту работу?
3 ответа
Проблема здесь в том, что Completed
событие возникает на каждом действии, но TaskCompletionSource
может быть завершено только один раз.
Вы все еще можете использовать местный TaskCompletionSource
(и ты должен). Вам просто нужно отменить регистрацию обратного вызова перед завершением TaskCompletionSource
, Таким образом, этот конкретный обратный вызов с этим конкретным TaskCompletionSource
больше никогда не будет звонить
public static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
EventHandler<CustomEventArg> callback = null;
callback = (sender, e) =>
{
service.Completed -= callback;
tcs.SetResult(e.Result);
};
service.Completed += callback;
service.RunAsync(parameter);
return tcs.Task;
}
Это также устранит возможную утечку памяти, когда ваша служба хранит ссылки на все эти делегаты.
Однако следует помнить, что вы не можете одновременно выполнять несколько таких операций. По крайней мере, если у вас нет способа сопоставить запросы и ответы.
Похоже, что MyService
поднимет Completed
событие более одного раза. это вызывает SetResult
быть вызванным более одного раза, что вызывает вашу ошибку.
У вас есть 3 варианта, которые я вижу. Измените событие Completed так, чтобы оно вызывалось только один раз (кажется странным, что вы можете выполнить его более одного раза), измените SetResult
в TrySetResult
поэтому он не выдает исключение, когда вы пытаетесь установить его во 2-й раз (это приводит к небольшой утечке памяти, так как событие все еще вызывается, и источник завершения все еще пытается быть установлен), или отписывается от события ( ответ i3arnon)
Альтернативное решение для i3arnon:
public async static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
EventHandler<CustomEventArg> callback =
(s, e) => tcs.SetResult(e.Result);
try
{
contacts.Completed += callback;
contacts.RunAsync(parameter);
return await tcs.Task;
}
finally
{
contacts.Completed -= callback;
}
}
Однако это решение будет иметь конечный автомат, сгенерированный компилятором. Это будет использовать больше памяти и процессора.