【问题标题】:Task.Run how it works [closed]Task.Run 它是如何工作的[关闭]
【发布时间】:2017-03-25 00:23:27
【问题描述】:

我猜很简单,但我在任务的工作中有些误解

当我调用下面的代码时,它运行良好,我会在大约 1 秒内得到结果。

return Task.Run(() => SendRequest<IADsystem[]>(path)).Result;

但是这个永远不会返回结果:

  Task<IADsystem[]> task = SendRequest<IADsystem[]>(path);
  task.Wait(); //also tried without wait
  return task.Result;

我想也许任务没有启动,我需要调用 Start(),但是当我完成它时,我得到了“Start may not be called on a promise-style task”异常。

SendRequest 方法:

 private async Task<T> SendRequest<T>(string requestUri) where T : class
    {
        var authHandler = new HttpClientHandler();
        authHandler.Credentials = CredentialCache.DefaultNetworkCredentials;

        using(var client = new HttpClient(authHandler))
        {
            client.BaseAddress = apiServerURI;
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(jsonAcceptType);

            HttpResponseMessage response = await client.GetAsync(requestUri);
            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsAsync<T>();
            }
            else
                return null;
        }
    }

请解释一下它是如何工作的

【问题讨论】:

  • 你试过var result = await SendRequest&lt;IADsystem[]&gt;(path);
  • 您的SendRequest 方法与多线程无关。你在哪里执行它,什么环境(控制台、Winforms、WPF、ASP:NET)?
  • 如果你从 UI 线程调用第二种方法,你就进入了死锁
  • @Fabio,ASP NET Web 窗体,等待相同的结果 - 永不返回
  • 这条线应该可以工作var result = await SendRequest&lt;IADsystem[]&gt;(path);。你说的“永远不会回来”是什么意思?你如何检查这永远不会返回

标签: c# multithreading asynchronous async-await task


【解决方案1】:

首先考虑Wait() 方法:

Task<IADsystem[]> task = SendRequest<IADsystem[]>(path);
task.Wait(); //also tried without wait
return task.Result;

此代码使用同步异步反模式。这可以cause a deadlock,正如我在我的博客中详细描述的那样。核心问题是您的代码阻塞了异步代码。这在某些情况下有效,但在这种情况下,您会看到常见的死锁。

总而言之,这是由于await 捕获了当前上下文(通常是SynchronizationContext),并使用它来恢复其async 方法。因此,如果您从 UI 线程(或 ASP.NET 请求上下文)调用此代码,则 SendRequest 中的 awaits 将尝试在该上下文中恢复,但该上下文被 Wait/@ 阻止987654331@,导致死锁。

请注意,问题是由于同步调用异步方法所致;理想的解决方案是“一路异步”,正如我在async best practices article 中所描述的那样。

回到实际问题:

Task.Run 是如何工作的

当我调用下面的代码时,它运行良好

return Task.Run(() => SendRequest<IADsystem[]>(path)).Result;

有点。它在没有死锁的意义上“起作用”。但是,它并不理想,因为它会在请求期间阻塞线程池线程。更好的解决方案是一直使用async

但暂时不谈,为什么它起作用的原因是因为它“超出”了调用上下文。 Task.Run 在线程池线程(没有 UI 或 ASP.NET 请求上下文的线程)上执行 SendRequest,因此 SendRequest 中的 awaits 在线程池线程上恢复。这就是为什么你的调用线程可以在没有死锁的情况下阻塞它。

但它仍然不应该阻塞任务;它应该改为await

【讨论】:

    【解决方案2】:

    async/await 方法必须一直是 async/await。如果您使用的是 Windows 窗体,那么任何包含调用 SendRequest 的代码的事件处理程序都必须标记为异步。 UI 事件处理程序和您的 SendRequest 之间的每个方法也必须标记为异步,并且必须等待它们。那你应该

    var result = await SendRequest<IADsystem[]>(path);
    

    就像法比奥建议的那样。

    Here 是关于 async/await 的更多信息。

    【讨论】:

      【解决方案3】:

      我在这个页面开始学习异步,所以我认为这是一个很好的起点。我敦促您查看执行上下文Async - Starting Point

      关于为什么你有一个死锁,我会试着解释一下。当您调用task.Wait() 时,您基本上是在告诉当前线程等待结果继续。所以现在你当前的线程,或者你的Execution Context 正在等待结果继续。让我们继续。当您调用task.Wait() 时,您输入了private async Task&lt;T&gt; SendRequest&lt;T&gt;(string requestUri)... 方法,当您到达return await response.Content.ReadAsAsync&lt;T&gt;(); 时,您传递了当前执行上下文,它正在等待来自SendRequest&lt;T&gt; 的结果。现在,你陷入了僵局。为什么?您的 UI 线程正在等待来自 SendRequest&lt;T&gt; 的答复,为了让 SendRequest&lt;T&gt; 完成(或 return),它需要访问 UI 线程但您无法访问 UI 线程,因为它正在等待 SendRequest&lt;T&gt;要完成,但要让SendRequest&lt;T&gt; 完成它需要访问 UI 线程....

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多