17

我想用来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只调用 is 的事件处理程序(每次都使用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); 
}

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

4

3 回答 3

32

这里的问题是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;
}

这也将解决您的服务保留对所有这些委托的引用时可能出现的内存泄漏。

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

于 2015-08-03T13:06:08.803 回答
3

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

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

于 2015-08-03T13:34:21.853 回答
3

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

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

于 2015-08-03T18:43:38.170 回答