【问题标题】:Async Task not returning to main thread异步任务不返回主线程
【发布时间】:2017-07-13 02:16:17
【问题描述】:

我认为这与我非常糟糕的异步编程有关。

来了。我正在使用异步方法获取 mailchimp 订阅者,但结果只是挂起并且永远不会返回到主线程。

异步方法

public async Task<List<Subscriber>> GetMailChimpSubscribers()
{
    TaskCompletionSource<List<Subscriber>> result = new TaskCompletionSource<List<Subscriber>>();
    await Task.Run(async () =>
    {
        var subscribers = new List<Subscriber>();
        var listId = "";
        var members = await _manager.Members.GetAllAsync(listId);
        foreach (var item in members)
        {
            var sub = new Subscriber();
            sub.Email = item.EmailAddress;
            subscribers.Add(sub);
        }
        result.SetResult(subscribers);
    });
    return result.Task.Result;
}

这在 result.SetResult(subscribers) 语句之后完全挂起。

这是从

调用的
public static List<Subscriber> GetSubscribers()
{
    MailchimpHelper helper = new MailchimpHelper();
    var subscribers= helper.GetMailChimpSubscribers();

    return subscribers.Result;
}

这里到底出了什么问题?设置错了吗?

PS:mailchimp 或 api 没有问题,它在控制台中运行良好。这对于异步编程来说纯粹是一件坏事

更新:

如果有人遇到这种情况。该博客帮助清除了很多

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

以下答案和博客中的工作解决方案。

public async Task<List<Subscriber>> GetMailChimpSubscribers()
   {
    var subscribers = new List<Subscriber>();
    var listId = "";
    var members = 
     await   _manager.Members.GetAllAsync(listId).**ConfigureAwait(false)**;

    foreach (var item in members)
    {
        var sub = new Subscriber();
        sub.Email = item.EmailAddress;
        subscribers.Add(sub);
    }

    return subscribers;


} 

public static List<Subscriber> GetSubscribers()
{
    MailchimpHelper helper = new MailchimpHelper();
    var subscribers= helper.GetMailChimpSubscribers();
    subscribers.Wait();

    return subscribers.Result;
}

【问题讨论】:

  • 您的GetSubscribers() 方法也必须是async,并且您必须await 调用helper.GetMailChimpSubscribers() 而不是调用.Result
  • 另外,在 await Task.Run(async () =&gt; 中包装一个已经异步的调用是没有用的。
  • @PeterBons,严格来说这不是真的,因为 Task.Run 将安排异步匿名方法在后台线程中运行。不使用时,该方法将在调用线程上执行,这可能是 UI 线程。
  • 为清楚起见,您能否标记(或指定)您正在使用的环境(ASP.net、WPF 等)? (只是明确说明是否存在同步上下文以及是否可以使用async“一直向下”)。
  • 关于编辑:由于缺少同步上下文,我希望这可以在控制台应用程序中工作。它“宣传不佳”,但异步在控制台应用程序中的工作方式与它在具有同步上下文的环境中的工作方式大不相同。

标签: c# asp.net-mvc async-await


【解决方案1】:

这里有很多错误:

删除所有不好的部分给我们留下:

public async Task<List<Subscriber>> GetMailChimpSubscribersAsync()
{
  var subscribers = new List<Subscriber>();
  var listId = "";
  var members = await _manager.Members.GetAllAsync(listId);
  foreach (var item in members)
  {
    var sub = new Subscriber();
    sub.Email = item.EmailAddress;
    subscribers.Add(sub);
  }
  return subscribers;
}

public static async Task<List<Subscriber>> GetSubscribersAsync()
{
  MailchimpHelper helper = new MailchimpHelper();
  return await helper.GetMailChimpSubscribersAsync();
}

【讨论】:

  • 看起来你打败了我推荐了关于为什么你不应该阻止异步的文章:) - 这是迄今为止我所知道的关于为什么这是一个坏主意的最好的文章,顺便说一句,它绝对配得上它的规范地位。
  • 虽然这个答案是正确的,但我会像在@EJoshuaS 答案上所做的那样评论:这假设 GetSubscribersAsync 的入口点可以是异步方法
  • @CamiloTerevinto 事实上,这种死锁有点暗示存在同步上下文(尽管 OP 并没有明确说明这一点)。
  • @MandarJogalekar 从剃刀文件调用代码没有意义,为什么不从控制器操作调用它并将其传递给视图模型?或者你为什么不从客户端发出 ajax 请求?
  • @MandarJogalekar:使用ConfigureAwait(false) 阻止 ASP.NET 线程并不是一个理想的解决方案。相反,按照 maccettura 评论的方式进行操作并在操作方法中完成所有异步工作。当它到达视图时,您应该有一个普通的 'ol viewmodel 对象。
【解决方案2】:

不要在具有同步上下文的环境中使用 .Result - 它会创建循环等待。

一般情况下,如果可能,请尝试“一直使用”async - 即,如果您打算使用 async/await,您应该使用async/await.

请参阅 Stephen Cleary 的 Don't Block on Async Code 了解更多详情。

【讨论】:

  • 虽然非常鼓励一直向下异步(而且确实如此),但如果入口点不是异步的,那么您不能一直使用异步。这个答案假设入口点可以是异步的
  • @CamiloTerevinto 确实如此,但这通常发生在控制台应用程序或另一个缺少同步上下文的环境中(我在回答中假设了这一点)。不过,我确实包含了“如果可能”的警告来澄清这一点。
  • WPF 怎么样?入口点:UI,具有同步上下文,不能异步(除非异步 void 处理程序引发其他问题)
  • @CamiloTerevinto 确实如此-您是正确的,在帖子中没有明确说明 OP 是否可以“一直向下”执行异步(我确实进行了编辑以包含“如果可能”的免责声明以说明OP 无法做到这一点的可能性)。不过,如果可能的话,这样做会好得多。 (不过,async void 是一个单独的问题 - 这可能会导致竞争条件,但不会导致死锁)。
  • 我的评论似乎不适用,因为 OP 评论说入口点是 Razor 视图(我添加了 asp.net-mvc 标签)
【解决方案3】:

问题的第一部分是,出于某种奇怪的原因,您将任务包装在另一个任务中。将您的方法更改为:

public async Task<List<Subscriber>> GetMailChimpSubscribers()
{
    var subscribers = new List<Subscriber>();
    var listId = "";
    var members = await _manager.Members.GetAllAsync(listId);
    foreach (var item in members) //this foreach could be a simpler LinQ statement
    {
        var sub = new Subscriber();
        sub.Email = item.EmailAddress;
        subscribers.Add(sub);
    }

    return subscribers;
}

您还应该使用 await 调用此方法,但如果这不可能,则将您的方法更改为:

public static List<Subscriber> GetSubscribers()
{
    MailchimpHelper helper = new MailchimpHelper();
    var subscribers = helper.GetMailChimpSubscribers();
    subscribers.Wait();
    return subscribers.Result;
}

【讨论】:

  • 这样,它仍然挂在 var members = await _manager.Members.GetAllAsync(listId);只有当我把 Task 放在它周围时(我同意这是一个坏主意),它至少会向我显示结果;)
  • @MandarJogalekar 你能包含 GetAllAsync 的代码吗?那里可能还有另一个问题
  • @MandarJogalekar 很高兴它成功了。看看将 foreach 的大小调整为 linq 单线 ;)
  • 所以你指出 OP 不应该在一个任务中不必要地包装一个任务,然后去添加更多的代码来不必要地在另一个任务中包装一个任务......
  • @CamiloTerevinto 它有什么不同?在这两种情况下,您只是在另一个线程中启动一个异步操作,而您可以很容易地让它在当前线程中启动,因为它是异步的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-12
  • 2014-10-01
相关资源
最近更新 更多