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

我想使用TaskCompletionSource来包装MyService这是一个简单的服务:

 public static Task ProcessAsync(MyService service, int parameter) { var tcs = new TaskCompletionSource(); //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变量),因此它将执行两次! 并且第二次抛出此exception:

在已经完成时尝试转换任务最终状态

我不确定,我应该将tcs声明为类级变量,如下所示:

 TaskCompletionSource tcs; public static Task ProccessAsync(MyService service, int parameter) { tcs = new TaskCompletionSource(); service.Completed -= completedHandler; service.Completed += completedHandler; return tcs.Task; } private void completedHandler(object sender, CustomEventArg e) { tcs.SetResult(e.Result); } 

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

这里的问题是每个操作都会引发Completed事件,但TaskCompletionSource只能完成一次。

您仍然可以使用本地TaskCompletionSource (您应该)。 您只需在完成TaskCompletionSource之前取消注册回调。 这样,永远不会再次调用具有此特定TaskCompletionSource特定回调:

 public static Task ProcessAsync(MyService service, int parameter) { var tcs = new TaskCompletionSource(); EventHandler callback = null; callback = (sender, e) => { service.Completed -= callback; tcs.SetResult(e.Result); }; service.Completed += callback; service.RunAsync(parameter); return tcs.Task; } 

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

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

i3arnon 答案的另一种解决方案是:

 public async static Task ProcessAsync(MyService service, int parameter) { var tcs = new TaskCompletionSource(); EventHandler callback = (s, e) => tcs.SetResult(e.Result); try { contacts.Completed += callback; contacts.RunAsync(parameter); return await tcs.Task; } finally { contacts.Completed -= callback; } } 

但是,此解决方案将具有编译器生成的状态机。 它将使用更多的内存和CPU。

似乎MyService将多次提升Completed事件。 这会导致多次调用SetResult ,从而导致错误。

我看到你有3个选项。 将Completed事件更改为仅提升一次(看起来很奇怪,您可以多次完成),将SetResult更改为TrySetResult这样当您尝试将其设置为第二次时它不会引发exception(这确实会引入一个小的内存泄漏事件仍然被调用,完成源仍然尝试设置),或取消订阅事件( i3arnon的答案 )