问题描述
我有一个从旧代码返回类型为 IAsyncResult
的现有方法,如何从该方法中调用异步任务?例如:
public IAsyncResult BeginXXX()
{
// I want to call my async Task here,but it cannot compile like this:
var value = await DoMyTaskAsync();
DoSomethingWithValue(value);
// existing code
return new SomeOtherAsyncResult();
}
编辑:解决方案最好不必重构方法签名,这将涉及超出范围的工作。
解决方法
由于 Task
派生自 IAsyncResult
,
public class Task : IThreadPoolWorkItem,IAsyncResult,IDisposable ...
将重构后的代码包装在一个 Task.Run
中,以便维护当前的 API 并且仍然能够使用 async-await
public IAsyncResult BeginXXX() {
return Task.Run(async () => {
var value = await DoMyTaskAsync(); // I want to call my async Task here
DoSomethingWithValue(value);
// existing code
return new SomeOtherAsyncResult();
});
}
这应该适用于所示示例的范围。
我强烈建议这只是一个临时修复,因为这可能会导致堆栈死锁,混合异步调用和阻塞调用。
这段代码和依赖它的代码最终应该被重构,以遵循为异步代码提供的通用文档的建议。
,我有一个从旧代码返回类型为 IAsyncResult 的现有方法,如何从该方法中调用异步任务?
答案在 TAP interop documentation 中。基本上你返回一个 TaskCompletionSource<T>.Task
。
这里有一些helpers from my AsyncEx library可以让这件事变得更容易:
public static IAsyncResult ToBegin<TResult>(Task<TResult> task,AsyncCallback callback,object state)
{
var tcs = new TaskCompletionSource<TResult>(state,TaskCreationOptions.RunContinuationsAsynchronously);
var oldContext = SynchronizationContext.Current;
SynchronizationContext.Current = null;
try
{
CompleteAsync(task,callback,tcs);
}
finally
{
SynchronizationContext.Current = oldContext;
}
return tcs.Task;
}
// `async void` is on purpose,to raise `callback` exceptions directly on the thread pool.
private static async void CompleteAsync<TResult>(Task<TResult> task,TaskCompletionSource<TResult> tcs)
{
try
{
tcs.TrySetResult(await task.ConfigureAwait(false));
}
catch (OperationCanceledException ex)
{
tcs.TrySetCanceled(ex.CancellationToken);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
finally
{
callback?.Invoke(tcs.Task);
}
}
public static TResult ToEnd<TResult>(IAsyncResult asyncResult) =>
((Task<TResult>)asyncResult).GetAwaiter().GetResult();
请注意,TaskCompletionSource<T>
是为您的 state
参数提供正确语义所必需的。
有了这些,您的实现可能如下所示:
public IAsyncResult BeginXXX(...rest,object state) =>
XXXAsync(...rest).ToBegin(callback,state);
public EndXXX(IAsyncResult asyncResult) => TaskExtensions.ToEnd<MyResult>(asyncResult);
public async Task<MyResult> XXXAsync(...rest)
{
var value = await DoMyTaskAsync();
DoSomethingWithValue(value);
return myResult;
}
请注意,这里假设您要将这个级别的所有内容都翻译成 async
/await
并去掉 new SomeOtherAsyncResult
。但是,如果您没有准备好转换为 IAsyncResult
/async
的较低级别的基于 await
的 API 并且仍然需要调用,那么您的 {{1} } 方法可以使用TAP-over-APM:
XXXAsync
在这种情况下,您最终会使用 APM-over-TAP(以保留您的 public async Task<MyResult> XXXAsync(...rest)
{
var value = await DoMyTaskAsync();
DoSomethingWithValue(value);
return await TaskFactory.FromAsync(BeginOtherAsyncResult,EndOtherAsyncResult,...restOtherAsyncResultArgs,null);
}
接口)和 TAP-over-APM(以继续使用 BeginXXX
接口),并将您的 TAP 方法夹在中间(OtherAsyncResult
) 之间。
这是一个常见的问题。您可以使用 TaskCompletionSource
解决此问题。
假设您有一个遗留类,它具有旧式异步方法 BeginBar
和 EndBar
。 BeginBar
接受 AsyncCallback
委托。
public Task<int> Foo()
{
var source = new TaskCompletionSource<int>();
someLegacyClass.BeginBar( x => source.TrySetResult(someLegacyClass.EndBar(x)); }
return source.Task;
}
见interop with other asynchronous patterns
,在调用 DoMyTaskAsync
函数时,您“可以”忽略 Asynchronous Programming 中的所有优点并执行以下操作:
public IAsyncResult BeginXXX()
{
// I want to call my async Task here,but it cannot compile like this:
var value = DoMyTaskAsync().Result;
DoSomethingWithValue(value);
// existing code
return new SomeOtherAsyncResult();
}
或者,您可以阅读 async
对函数的作用。如果您在调用它时不使用 DoMyTaskAsync
,那么所有 await
确实会返回一个 Task<T>
实例。
您可以正常操作Task
实例,而无需依赖await
。阅读Task-based Asynchronous Programming
您的函数定义中缺少 async
关键字。
您还需要返回 Task<T>
。
尝试这样的事情:
public async Task<IAsyncResult> BeginXXX()
{
await DoMyTaskAsync();
// the rest of your code
}