TaskCompletionSource抛出"尝试将任务转换为已完成的最终状态"

Hos*_*Rad 15 .net c# asynchronous async-await 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;
}
Run Code Online (Sandbox Code Playgroud)

这段代码第一次运行良好.但是我第二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); 
}
Run Code Online (Sandbox Code Playgroud)

我必须使用不同的返回类型包装许多方法,这样我必须编写丢失的代码,变量,事件处理程序,所以我不确定这是否是这种情况下的最佳实践.那么有更好的方法来完成这项工作吗?

i3a*_*non 26

这里的问题是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;
}
Run Code Online (Sandbox Code Playgroud)

这也将解决当您的服务保持对所有这些委托的引用时可能发生的内存泄漏.

您应该记住,虽然您不能同时运行多个这些操作.除非您有办法匹配请求和响应,否则至少不会.


Sco*_*ain 5

看来会多次MyService引发该事件。Completed这会导致SetResult被多次调用,从而导致您的错误。

我看到你有 3 个选择。将 Completed 事件更改为仅引发一次(似乎很奇怪,您可以完成多次),更改SetResultTrySetResult这样,当您尝试第二次设置它时,它不会引发异常(这确实会引入一个小的内存泄漏,因为事件仍然被调用并且完成源仍然尝试设置),或者取消订阅事件(i3arnon 的答案

  • @HosseinNarimaniRad 不,不是。每次执行该行时都会有一个新的引用。就像每次调用该方法都有一个新的 CTS 一样。 (2认同)