TaskCompletionSource throws “An attempt was made to transition a task to a final state when it had already completed”

浪尽此生 提交于 2019-12-03 05:32:16

The issue here is that the Completed event is raised on each action but the TaskCompletionSource can only be completed once.

You can still use a local TaskCompletionSource (and you should). You just need to unregister the callback before completing the TaskCompletionSource. That way this specific callback with this specific TaskCompletionSource will never be called again:

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

This will also solve the possible memory leak that you have when your service keeps references to all these delegates.

You should keep in mind though that you can't have multiple of these operations running concurrently. At least not unless you have a way to match requests and responses.

Paulo Morgado

An alternative solution to i3arnon's answer would be:

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

However, this solution will have a compiler generated state machine. It will use more memory and CPU.

Scott Chamberlain

It appears that MyService will raise the Completed event more than once. this causes SetResult to be called more than once which causes your error.

You have 3 options that I see. Change the Completed event to only be raised once (Seems odd that you can complete more than once), change SetResult to TrySetResult so it does not throw a exception when you try to set it a 2nd time (this does introduce a small memory leak as the event still gets called and the completion source still tries to be set), or unsubscribe from the event (i3arnon's answer)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!