TaskCompletionSource抛出“当任务已经完成时,尝试将任务转换到最终状态”

本文关键字:任务 转换 状态 抛出 TaskCompletionSource 完成时 | 更新日期: 2023-09-27 18:06:09

我想用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); 
}

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

TaskCompletionSource抛出“当任务已经完成时,尝试将任务转换到最终状态”

这里的问题是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个选项。将完成的事件更改为仅提出一次(似乎奇怪的是,你可以完成不止一次),将SetResult更改为TrySetResult,因此当您尝试第二次设置它时不会抛出异常(这确实引入了一个小的内存泄漏,因为事件仍然被调用并且完成源仍然试图设置),或取消订阅事件(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;
    }
}

然而,这个解决方案将有一个编译器生成的状态机。