如何在C#中从同步方法调用异步方法?


861

我有一个public async void Foo()要从同步方法调用的方法。到目前为止,我从MSDN文档中看到的所有内容都是通过异步方法调用异步方法,但是我的整个程序不是使用异步方法构建的。

这有可能吗?

这是从异步方法调用这些方法的一个示例:http : //msdn.microsoft.com/zh-cn/library/hh300224(v=vs.110).aspx

现在,我正在研究从同步方法调用这些异步方法。


2
我也遇到了这个问题。重写RoleProvider时,您无法更改GetRolesForUser方法的方法签名,因此无法使该方法异步,因此不能使用await异步调用api。我的临时解决方案是将同步方法添加到我的通用HttpClient类中,但想知道这是否可行(以及可能产生的影响)。
蒂莫西·李·罗素

1
因为您的async void Foo()方法不返回a,Task这意味着调用者无法知道它何时完成,因此必须返回Task

1
链接有关如何在UI线程上执行此操作的相关问题。
noseratio

Answers:


710

异步编程确实在代码库中“增长”。人们已经将其与僵尸病毒进行了比较。最好的解决方案是允许它增长,但是有时这是不可能的。

我在Nito.AsyncEx库中编写了一些类型,用于处理部分异步的代码库。但是,没有一种解决方案可以在每种情况下都适用。

解决方案A

如果您有一个简单的异步方法,不需要同步回其上下文,则可以使用Task.WaitAndUnwrapException

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

希望使用Task.WaitTask.Result因为包装在异常AggregateException

仅当MyAsyncMethod不同步回其上下文时,此解决方案才适用。换句话说,每个awaitin MyAsyncMethod都应以结尾ConfigureAwait(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.WaitAndUnwrapException是因为WinForms / WPF / SL / ASP.NET上发生了相当细微的死锁:

  1. 同步方法调用异步方法,获得Task
  2. 同步方法对进行阻塞等待Task
  3. async方法await不使用ConfigureAwait
  4. Task这种情况无法完成,因为它只有在完成async方法完成; 该async方法无法完成,因为它正尝试将其继续安排到SynchronizationContext,并且WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已在该上下文中运行。

这就是为什么ConfigureAwait(false)在每种async方法中尽可能多使用一个好主意的原因之一。

解决方案C

AsyncContext.RunTask并非在所有情况下都有效。例如,如果该async方法等待需要UI事件完成的操作,那么即使使用嵌套上下文,您也将死锁。在这种情况下,您可以async在线程池上启动该方法:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

但是,此解决方案需要一个MyAsyncMethod可以在线程池上下文中工作的。因此,它无法更新UI元素或访问ASP.NET请求上下文。在这种情况下,您也可以添加ConfigureAwait(false)await语句,并使用解决方案A。

更新,2019-05-01:MSDN文章在此处提供了当前的“最差实践” 。


9
解决方案A看起来像我想要的,但看起来像task.WaitAndUnwrapException()并没有纳入.Net 4.5 RC;它只有task.Wait()。知道如何在新版本中执行此操作吗?还是这是您编写的自定义扩展方法?
致命的狗狗2012年

3
WaitAndUnwrapException是我自己的AsyncEx库中的方法。正式的.NET库在混合同步和异步代码方面没有提供太多帮助(通常,您不应该这样做!)。我正在等待.NET 4.5 RTW和一台新的非XP笔记本电脑,然后将AsyncEx更新为可在4.5上运行(我目前无法为4.5进行开发,因为我在XP上呆了几周)。
史蒂芬·克利西

12
AsyncContext现在有一个Run采用lambda表达式的方法,因此您应该使用var result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary 2013年

1
我从Nuget那里获得了您的图书馆,但实际上似乎没有RunTask方法。我能找到的最接近的东西是Run,但是没有Result属性。
Asad Saeeduddin


313

添加最终解决了我的问题的解决方案,希望可以节省一些时间。

首先阅读Stephen Cleary的几篇文章:

根据“不要阻止异步代码”中的“两个最佳实践”,第一个不适用于我,第二个不适用于(基本上,如果我可以使用await,我可以!)。

所以这是我的解决方法:将呼叫包装在a内Task.Run<>(async () => await FunctionAsync());,希望不再出现死锁

这是我的代码:

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

5
两年过去了,我很好奇这个解决方案的效果如何。任何新闻?新手会迷失这种方法的微妙之处吗?
丹·埃斯帕萨

26
这不会死锁,是的,只是因为它被迫在始发线程的同步上下文之外的新线程中运行。但是,在某些情况下,这是非常不明智的选择:尤其是Web应用程序。这可以有效地将Web服务器的可用线程减半(一个用于请求的线程,一个用于该请求的线程)。您做的越多,效果就越差。您可能最终会死锁整个Web服务器。
克里斯·普拉特

29
@ChrisPratt-您可能是对的,因为Task.Run()这不是异步代码中的最佳做法。但是,再次,原始问题的答案是什么?永远不要同步调用异步方法?我们希望,但是在现实世界中,有时我们必须这样做。
Tohid

1
@Tohid,您可以尝试使用Stephen Cleary的图书馆。我已经看到人们认为这是事实,并且Parallel.ForEach滥用不会对“现实世界”产生影响,最终导致服务器瘫痪。对于控制台应用程序,此代码是可以的,但正如@ChrisPratt所说,不应在Web Apps中使用。它可能“现在”有效,但不可扩展。
makhdumi

1
我很高兴开始在帐户中创建新帐户,以便回答问题,以获取足够的分数来支持该帐户....
Giannis Paraskevopoulos

206

Microsoft建立了一个AsyncHelper(内部)类来将Async作为Sync运行。源看起来像:

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

Microsoft.AspNet.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的Microsoft许可。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
我的异步方法正在等待其他异步方法。我不会用来装饰任何await电话ConfigureAwait(false)。我尝试使用AsyncHelper.RunSyncApplication_Start()Global.asax中的函数调用异步函数,它似乎可以正常工作。这是否意味着我AsyncHelper.RunSync确实不容易在本帖子的其他地方读到“容易发回给调用者的上下文”的死锁问题?
Bob.at.Indigo.Health 2015年

1
@ Bob.at.SBS取决于您的代码功能。我可以安全地使用它而不是简单地使用此代码。这是同步运行异步命令的最小和半安全的方法,很容易不适当地使用它来导致死锁。
Erik Philips

1
谢谢。2个后续问题:1)您能否举一个异步方法想要避免的事情的例子,否则会导致死锁; 2)在这种情况下,死锁通常取决于时间吗?如果它在实践中可行,我的代码中是否仍会存在与时序有关的死锁?
Bob.at.Indigo.Health

@ Bob.at.SBS我建议使用右上方的“ 提问”按钮提问。您可以在此问题中包含指向该问题或答案的链接作为参考。
Erik Philips

1
@ Bob.at ... Erik提供的代码在Asp下可完美工作。NET MVC5和EF6,但是当我尝试使用其他任何解决方案(ConfigureAwait(false).GetAwaiter()。GetResult()或.result)时却没有挂起,它完全挂在我的Web应用程序上
LeonardoX

150

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

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

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


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

您会在许多Microsoft文档中看到它,例如:https : //docs.microsoft.com/zh-cn/azure/service-bus-messaging/service-bus-dotnet-how-to-use-主题-订阅


11
我不知道为什么有人对此表示反对。这对我来说很棒。如果没有此修复程序,我将不得不在任何地方传播ASYCH。
囚犯零

11
为什么比这更好MainAsync().Wait()
2016年

8
我同意。您只需要MainAsync()。Wait()即可。
Hajjat

8
@crush我正在描述如何避免死锁。在某些情况下,从UI或asp.net线程调用.Wait()会导致死锁。异步僵局
David

6
@ClintB:绝对不要在ASP.NET Core中执行此操作。Web应用程序特别容易出现线程不足的情况,每次执行此操作时,都会从池中拉出一个线程,否则该线程将用于服务请求。对于台式机/移动应用程序而言,它的问题较少,因为它们通常是单用户的。
克里斯·普拉特

52
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

}

您将关键字“ await”读为“启动此长期运行的任务,然后将控制权返回给调用方法”。长时间运行的任务完成后,它将在其后执行代码。等待之后的代码类似于以前的CallBack方法。最大的区别在于逻辑流程不会被打断,这使得写入和读取变得更加容易。


15
Wait包装异常并可能导致死锁。
Stephen Cleary 2012年

我以为,如果不使用调用异步方法await,它将被同步执行。至少对我有用(无需调用myTask.Wait)。实际上,尝试调用时出现了异常,myTask.RunSynchronously()因为它已经执行了!
敬畏

2
我喜欢这个答案。良好的编辑注释,小巧而优雅。感谢您的贡献!我仍在学习并发性,因此一切都可以帮助:)
kayleeFrye_onDeck

2
到今天为止,该答案是否仍然有效?我只是在MVC Razor项目中尝试过,而该应用程序只是在访问时挂起.Result
Gone Coding

7
@TrueBlueAussie这是同步上下文死锁。您的异步代码将编组回同步上下文,但是当时被Result调用阻塞,因此它永远不会到达那里。而且Result永远不会结束,因为它正在等待某个正在等待Result结束的人,基本上:D
Luaan 2015年

40

我不确定100%,但是我相信此博客中描述的技术在许多情况下都可以使用:

因此,task.GetAwaiter().GetResult()如果您想直接调用此传播逻辑,则可以使用。


6
上面斯蒂芬·克雷里(Stephen Cleary)的答案中的解决方案A 使用此方法。请参阅WaitAndUnwrapException源。
Orad

如果要调用的函数无效或任务,是否需要使用GetResult()?我的意思是,如果您不想得到任何结果
batmaci

是的,否则它将在任务完成之前不会阻塞。另外,也可以不调用GetAwaiter()。GetResult(),而可以调用
.Wait

1
那是“许多情况”部分。这取决于整体线程模型以及其他线程在做什么,以确定是否存在死锁风险。
NStuke

24

但是,有一个很好的解决方案可以在每种情况下(几乎:请参见注释)工作:即席消息泵(SynchronizationContext)。

调用线程将按预期方式被阻止,同时仍确保从async函数调用的所有连续都不会死锁,因为它们将被封送到调用线程上运行的临时SynchronizationContext(消息泵)中。

临时消息泵助手的代码:

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

有关异步泵的更多详细信息,请参见此处



这在Asp.net方案中不起作用,因为您可能会随机丢失HttpContext.Current。
乔什·穆奇

12

对于任何关注这个问题的人来说...

如果您查看的Microsoft.VisualStudio.Services.WebApi是一个名为的类TaskExtensions。在该类中,您将看到静态扩展方法Task.SyncResult(),该方法完全像阻塞线程一样,直到任务返回。

在内部,它调用起来task.GetAwaiter().GetResult()非常简单,但是在任何async可以返回的方法上工作TaskTask<T>或者Task<HttpResponseMessage>...语法糖,宝贝...爸爸都喜欢吃甜食。

看起来...GetAwaiter().GetResult()是在阻塞上下文中执行异步代码的MS官方方法。对于我的用例来说似乎工作得很好。


3
你让我“完全像砖块一样”。
戴伍德·本·卡里姆

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

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

或使用此:

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

6

您可以从同步代码中调用任何异步方法,也就是说,直到需要await使用它们为止,在这种情况下,也必须对其进行标记async

正如很多人在这里建议的那样,您可以在同步方法中对结果任务调用Wait()或Result,但是最后在该方法中导致阻塞调用,这使异步的目的无效。

我真的不能创建您的方法,async并且您不想锁定同步方法,那么您将不得不通过将回调方法作为参数传递给任务上的ContinueWith方法来使用回调方法。


5
那现在不会同步调用该方法吗?
Jeff Mercado 2012年

2
据我了解,问题是您可以从非异步方法调用异步方法吗?这并不意味着必须以阻塞的方式调用异步方法。
2012年

抱歉,您的“他们也必须被标记async”使我的注意力从您真正说的话转移开了。
杰夫·梅卡多

如果我不是很在乎异步性,可以这样调用吗(斯蒂芬·克莱里一直keep绕着包装异常中的死锁可能性如何?)我有一些测试方法(必须同步执行)测试异步方法。在继续之前,我必须等待结果,以便可以测试异步方法的结果。
敬畏

6

我知道我来晚了。但是,如果像我这样的人想要以一种整洁,简单的方式解决此问题,而又不必依赖其他库。

我从Ryan找到了以下代码

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

那么你可以这样称呼它

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
这看起来与上面的答案完全一样,是否我错过了某些内容
inlokesh

2

经过数小时的尝试不同方法的尝试,或多或少都取得了成功,这就是我的结局。它在获取结果时不会死锁,它还会获取并抛出原始异常,而不是包装的异常。

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

与返回task.GetAwaiter()。GetResult();一起使用;
每G

是的,但是原始异常呢?
吉日·赫尔尼克

。结果,我认为是基本上如.GetAwaiter相同()调用getResult()。
每克

-2

可以从新线程中调用(不是从线程池中调用!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

这些Windows异步方法有一个漂亮的小方法,称为AsTask()。您可以使用它使该方法作为任务返回自身,以便您可以在其上手动调用Wait()。

例如,在Windows Phone 8 Silverlight应用程序上,您可以执行以下操作:

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 
    // .....
}

希望这可以帮助!


-4

如果要运行它同步

MethodAsync().RunSynchronously()

3
此方法用于启动冷任务。通常,异步方法返回一个热门任务,换句话说,一个已经开始的任务。调用RunSynchronously()热门任务会导致失败InvalidOperationException。尝试使用以下代码:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias,

-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

2
产生死锁。最好删除答案。
PreguntonCojoneroCabrón

Task.Run(()=> SaveAssetDataAsDraft())。Result; -不会产生死锁
Anubis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.