如何在C#中从同步方法调用异步方法?
我有一个如何在C#中从同步方法调用异步方法?,c#,async-await,C#,Async Await,我有一个公共异步void Foo()方法,我想从同步方法调用它。到目前为止,我在MSDN文档中看到的只是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的 这可能吗 下面是从异步方法调用这些方法的一个示例: 现在我正在研究从sync方法调用这些异步方法。您可以从同步代码调用任何异步方法,也就是说,直到您需要等待它们,在这种情况下,它们也必须标记为async 正如很多人在这里建议的那样,您可以在同步方法中对生成的任务调用Wait()或Result,但最终在该方法中使用阻塞调用,这有点
公共异步void Foo()
方法,我想从同步方法调用它。到目前为止,我在MSDN文档中看到的只是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的
这可能吗
下面是从异步方法调用这些方法的一个示例:现在我正在研究从sync方法调用这些异步方法。您可以从同步代码调用任何异步方法,也就是说,直到您需要等待它们,在这种情况下,它们也必须标记为
async
正如很多人在这里建议的那样,您可以在同步方法中对生成的任务调用Wait()或Result,但最终在该方法中使用阻塞调用,这有点违背了异步的目的
如果您真的无法使方法异步
,并且不想锁定同步方法,则必须使用回调方法,将其作为参数传递给任务上的ContinueWith方法。公共异步任务StartMyTask()
public async Task<string> StartMyTask()
{
await Foo()
// code to execute once foo is done
}
static void Main()
{
var myTask = StartMyTask(); // call your method which will return control once it hits await
// now you can continue executing code here
string result = myTask.Result; // wait for the task to complete to continue
// use result
}
{
等福
//完成foo后要执行的代码
}
静态void Main()
{
var myTask=StartMyTask();//调用您的方法,该方法将在命中wait后返回控件
//现在您可以在这里继续执行代码了
string result=myTask.result;//等待任务完成后继续
//使用结果
}
您将'wait'关键字理解为“启动这个长时间运行的任务,然后将控制权返回给调用方法”。一旦长时间运行的任务完成,它就会在完成后执行代码。wait之后的代码类似于以前的回调方法。最大的区别在于逻辑流不会中断,这使得写和读更容易。异步编程确实会通过代码库“增长”。它一直是。最好的解决办法是让它成长,但有时这是不可能的 我在我的库中编写了一些类型,用于处理部分异步的代码库。然而,没有一种解决方案在任何情况下都能奏效 解决方案A 如果您有一个简单的异步方法,不需要同步回其上下文,那么您可以使用
Task.waitandunwapexception
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
您不想使用Task.Wait
或Task.Result
,因为它们将异常包装在aggregateeexception
中
仅当MyAsyncMethod
未同步回其上下文时,此解决方案才适用。换句话说,MyAsyncMethod
中的每个wait
都应该以configurewait(false)
结束。这意味着它无法更新任何UI元素或访问ASP.NET请求上下文
解决方案B
如果MyAsyncMethod
确实需要同步回其上下文,则可以使用AsyncContext.RunTask
提供嵌套上下文:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
*2014年4月14日更新:在库的最新版本中,API如下所示:
var result = AsyncContext.Run(MyAsyncMethod);
(在本例中可以使用
Task.Result
,因为RunTask
将传播Task
异常)
您可能需要使用AsyncContext.RunTask
而不是Task.waitandunwapexception
的原因是WinForms/WPF/SL/ASP.NET上发生了一种相当微妙的死锁可能性:
任务
任务执行阻塞等待
async
方法使用await
而不使用ConfigureAwait
任务
无法完成,因为它只有在异步
方法完成时才能完成;async
方法无法完成,因为它正在尝试将其继续调度到SynchronizationContext
,WinForms/WPF/SL/ASP.NET将不允许继续运行,因为同步方法已在该上下文中运行async
方法中尽可能多地使用ConfigureAwait(false)
是个好主意的原因之一
解决方案C
AsyncContext.RunTask
不适用于所有场景。例如,如果async
方法等待某个需要UI事件才能完成的操作,那么即使使用嵌套上下文,也会死锁。在这种情况下,可以在线程池上启动async
方法:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
但是,此解决方案需要在线程池上下文中工作的myasynchmethod
。因此,它无法更新UI元素或访问ASP.NET请求上下文。在这种情况下,您还可以将ConfigureAwait(false)
添加到其await
语句中,并使用解决方案A
更新,2019-05-01:目前的“最不糟糕的做法”是在一个标准中。我不是100%确定,但我相信中描述的技术在许多情况下都应该有效: 因此,如果要直接调用此传播逻辑,可以使用
task.GetAwaiter().GetResult()
Microsoft构建了一个AsyncHelper(内部)类,将异步作为同步运行。源代码如下所示:
internal static class AsyncHelper
{
private static readonly TaskFactory _myTaskFactory = new
TaskFactory(CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return AsyncHelper._myTaskFactory
.StartNew<Task<TResult>>(func)
.Unwrap<TResult>()
.GetAwaiter()
.GetResult();
}
public static void RunSync(Func<Task> func)
{
AsyncHelper._myTaskFactory
.StartNew<Task>(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
内部静态类AsyncHelper
{
私有静态只读任务工厂_myTaskFactory=new
TaskFactory(CancellationToken.None,
任务创建选项。无,
TaskContinuationOptions。无,
TaskScheduler.Default);
公共静态TResult RunSync(Func Func)
{
返回AsyncHelper.\u myTaskFactory
.StartNew(func)
.Unwrap()
.getwaiter()
.GetResult();
}
公共静态无效运行同步(Func Func)
{
AsyncHelper.\u myTaskFactory
斯塔特纽先生
public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}
public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
/*await stuff here*/
}
private void DeleteSynchronous(string path)
{
StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
t.Wait();
}
private void FunctionThatNeedsToBeSynchronous()
{
// Do some work here
// ....
// Delete something in storage synchronously
DeleteSynchronous("pathGoesHere");
// Do other work here
// .....
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Threading
{
/// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
public static class AsyncPump
{
/// <summary>Runs the specified asynchronous method.</summary>
/// <param name="asyncMethod">The asynchronous method to execute.</param>
public static void Run(Action asyncMethod)
{
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
var prevCtx = SynchronizationContext.Current;
try
{
// Establish the new context
var syncCtx = new SingleThreadSynchronizationContext(true);
SynchronizationContext.SetSynchronizationContext(syncCtx);
// Invoke the function
syncCtx.OperationStarted();
asyncMethod();
syncCtx.OperationCompleted();
// Pump continuations and propagate any exceptions
syncCtx.RunOnCurrentThread();
}
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
}
/// <summary>Runs the specified asynchronous method.</summary>
/// <param name="asyncMethod">The asynchronous method to execute.</param>
public static void Run(Func<Task> asyncMethod)
{
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
var prevCtx = SynchronizationContext.Current;
try
{
// Establish the new context
var syncCtx = new SingleThreadSynchronizationContext(false);
SynchronizationContext.SetSynchronizationContext(syncCtx);
// Invoke the function and alert the context to when it completes
var t = asyncMethod();
if (t == null) throw new InvalidOperationException("No task provided.");
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
// Pump continuations and propagate any exceptions
syncCtx.RunOnCurrentThread();
t.GetAwaiter().GetResult();
}
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
}
/// <summary>Runs the specified asynchronous method.</summary>
/// <param name="asyncMethod">The asynchronous method to execute.</param>
public static T Run<T>(Func<Task<T>> asyncMethod)
{
if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
var prevCtx = SynchronizationContext.Current;
try
{
// Establish the new context
var syncCtx = new SingleThreadSynchronizationContext(false);
SynchronizationContext.SetSynchronizationContext(syncCtx);
// Invoke the function and alert the context to when it completes
var t = asyncMethod();
if (t == null) throw new InvalidOperationException("No task provided.");
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
// Pump continuations and propagate any exceptions
syncCtx.RunOnCurrentThread();
return t.GetAwaiter().GetResult();
}
finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
}
/// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
private sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
/// <summary>The queue of work items.</summary>
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
/// <summary>The processing thread.</summary>
private readonly Thread m_thread = Thread.CurrentThread;
/// <summary>The number of outstanding operations.</summary>
private int m_operationCount = 0;
/// <summary>Whether to track operations m_operationCount.</summary>
private readonly bool m_trackOperations;
/// <summary>Initializes the context.</summary>
/// <param name="trackOperations">Whether to track operation count.</param>
internal SingleThreadSynchronizationContext(bool trackOperations)
{
m_trackOperations = trackOperations;
}
/// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
/// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
/// <param name="state">The object passed to the delegate.</param>
public override void Post(SendOrPostCallback d, object state)
{
if (d == null) throw new ArgumentNullException("d");
m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
}
/// <summary>Not supported.</summary>
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("Synchronously sending is not supported.");
}
/// <summary>Runs an loop to process all queued work items.</summary>
public void RunOnCurrentThread()
{
foreach (var workItem in m_queue.GetConsumingEnumerable())
workItem.Key(workItem.Value);
}
/// <summary>Notifies the context that no more work will arrive.</summary>
public void Complete() { m_queue.CompleteAdding(); }
/// <summary>Invoked when an async operation is started.</summary>
public override void OperationStarted()
{
if (m_trackOperations)
Interlocked.Increment(ref m_operationCount);
}
/// <summary>Invoked when an async operation is completed.</summary>
public override void OperationCompleted()
{
if (m_trackOperations &&
Interlocked.Decrement(ref m_operationCount) == 0)
Complete();
}
}
}
}
AsyncPump.Run(() => FooAsync(...));
public class LogReader
{
ILogger _logger;
public LogReader(ILogger logger)
{
_logger = logger;
}
public LogEntity GetLog()
{
Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
return task.Result;
}
public async Task<LogEntity> GetLogAsync()
{
var result = await _logger.GetAsync();
// more code here...
return result as LogEntity;
}
}
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
var result=result.GetAwaiter().GetResult().AccessToken
MethodAsync().RunSynchronously()
private ReturnType RunSync()
{
var task = Task.Run(async () => await myMethodAsync(agency));
if (task.IsFaulted && task.Exception != null)
{
throw task.Exception;
}
return task.Result;
}
public static TResult RunSync<TResult>(Func<Task<TResult>> method)
{
var task = method();
return task.GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> method)
{
var task = method();
task.GetAwaiter().GetResult();
}
RunSync(() => Foo());
var result = RunSync(() => FooWithResult());
void Synchronous Function()
{
Task.Run(Foo).Wait();
}
string SynchronousFunctionReturnsString()
{
return Task.Run(Foo).Result;
}
string SynchronousFunctionReturnsStringWithParam(int id)
{
return Task.Run(() => Foo(id)).Result;
}