TaskCompletionSource 抛出 "An attempt was made to transition a task to a final state when it had already completed"
TaskCompletionSource throws "An attempt was made to transition a task to a final state when it had already completed"
我想用 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
的 second 只是再次分配 Completed
的事件处理程序(每次都使用相同的 service
变量)并且因此它将执行两次!第二次抛出这个异常:
attempt transition task final state when already completed
我不确定,我是否应该像这样将 tcs
声明为 class 级别变量:
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);
}
我必须用不同的 return 类型包装许多方法,这样我就必须编写丢失的代码、变量、事件处理程序,所以我不确定这是否是这种情况下的最佳做法。那么有没有更好的方法来完成这项工作?
这里的问题是每个操作都会引发 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 个选项。将 Completed 事件更改为仅引发一次(看起来很奇怪,您可以多次完成),将 SetResult
更改为 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 的替代解决方案是:
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.
我想用 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
的 second 只是再次分配 Completed
的事件处理程序(每次都使用相同的 service
变量)并且因此它将执行两次!第二次抛出这个异常:
attempt transition task final state when already completed
我不确定,我是否应该像这样将 tcs
声明为 class 级别变量:
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);
}
我必须用不同的 return 类型包装许多方法,这样我就必须编写丢失的代码、变量、事件处理程序,所以我不确定这是否是这种情况下的最佳做法。那么有没有更好的方法来完成这项工作?
这里的问题是每个操作都会引发 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 个选项。将 Completed 事件更改为仅引发一次(看起来很奇怪,您可以多次完成),将 SetResult
更改为 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
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.