我有一个公共异步无效Foo()方法,我想从同步方法调用。到目前为止,我从MSDN文档中看到的都是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的。

这可能吗?

下面是一个从异步方法调用这些方法的例子: 演练:使用Async和Await访问Web (c#和Visual Basic)

现在我正在研究从sync方法调用这些async方法。


当前回答

您可以从同步代码中调用任何异步方法,直到您需要等待它们,在这种情况下,它们也必须被标记为异步。

正如很多人在这里建议的那样,您可以在同步方法中对结果任务调用Wait()或Result,但最终会在该方法中进行阻塞调用,这在某种程度上违背了异步的目的。

如果你真的不能让你的方法异步,你不想锁定同步方法,那么你将不得不使用一个回调方法,把它作为参数传递给任务上的ContinueWith()方法。

其他回答

async Main现在是c# 7.2的一部分,可以在项目的高级构建设置中启用。

对于c# < 7.2,正确的方法是:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

你会在很多微软文档中看到这个用法,例如: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions

微软构建了一个AsyncHelper(内部)类来作为同步运行Async。源代码如下所示:

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

identity基类只有Async方法,为了将它们调用为Sync,有一些类的扩展方法看起来像这样(示例用法):

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

对于那些关心代码许可条款的人,这里有一个非常相似的代码链接(只是在线程上增加了对文化的支持),其中有注释表明它是由微软授权的MIT。https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).Result?说真的,微软现在不鼓励调用TaskFactory。StartNew,因为它们都是等效的,一个比另一个更可读。

绝对不是。

答案很简单

.Unwrap().GetAwaiter().GetResult() != .Result

首先

是任务。结果与.GetAwaiter.GetResult()?

其次,unwrap()会导致Task的设置不阻塞被包装的任务。

谁会问这个问题呢

这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).GetAwaiter().GetResult()

那就得看情况了。

关于Task.Start(), Task.Run()和Task.Factory.StartNew()的使用

摘录:

的任务。Run使用taskcreateoptions。DenyChildAttach意思是子任务不能附加到父任务它使用TaskScheduler。默认值,这意味着在线程池中运行任务的线程将始终用于运行任务。 startnew使用TaskScheduler。Current表示当前线程的调度程序,它可能是TaskScheduler。违约但并非总是如此。

更多阅读:

指定同步上下文

ASP。NET Core SynchronizationContext

为了更加安全,像这样调用AsyncHelper不是更好吗?RunSync(async () => await AsyncMethod().ConfigureAwait(false));通过这种方式,我们告诉"内部"方法"请不要尝试同步到上层上下文并取消锁"

非常好的观点,就像大多数对象架构问题一样,这要视情况而定。

作为一个扩展方法,你想强迫绝对每个调用,还是让程序员使用该函数配置他们自己的异步调用?我可以看到call 3场景的用例;它很可能不是你在WPF中想要的,当然在大多数情况下是有意义的,但考虑到在ASP中没有上下文。Net Core如果你能保证它是ASP内部的。Net Core,那就无所谓了。

我不是100%确定,但我相信这篇博客中描述的技巧在许多情况下都适用:

因此,如果想直接调用这个传播逻辑,可以使用task.GetAwaiter(). getresult()。

var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

或者用这个:

var result=result.GetAwaiter().GetResult().AccessToken

这是最简单的解决办法。我在网上的某个地方看到它,我不记得在哪里了,但我一直在成功地使用它。它不会死锁调用线程。

    void SynchronousFunction()
    {
        Task.Run(Foo).Wait();
    }

    string SynchronousFunctionReturnsString()
    {
        return Task.Run(Foo).Result;
    }

    string SynchronousFunctionReturnsStringWithParam(int id)
    {
        return Task.Run(() => Foo(id)).Result;
    }