TaskCompletionSource бросает " была предпринята попытка перевести задачу в конечное состояние, когда она уже завершена"

Я хочу использовать 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;
}

этот код работает хорошо в первый раз. Но ... --11-->второй время я зову 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;
}

это также решит возможную утечку памяти, которая у вас есть, когда ваша служба хранит ссылки на все это делегаты съезда.

вы должны иметь в виду, что вы не можете иметь несколько из этих операций выполняются одновременно. По крайней мере, если у вас нет способа сопоставить запросы и ответы.


альтернатива i3arnon ' s ответ будет:

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;
    }
}

однако это решение будет иметь компилятор, созданный машиной состояния. Он будет использовать больше памяти и процессора.


получается, что MyService поднимать Completed событие более одного раза. это вызывает SetResult вызывается более одного раза, что вызывает вашу ошибку.

у вас есть 3 варианта, которые я вижу. Измените завершенное событие только один раз (кажется странным, что вы можете завершить более одного раза), измените SetResult to TrySetResult таким образом, он не выдает исключение при попытке установить его во 2-й раз (это вводит небольшую утечку памяти, поскольку событие все еще вызывается и источник завершения все еще пытается быть установлен), или отписаться от события (i3arnon это)