这"Synchronous"代码会导致死锁吗?



我的公司有一个他们编写的Nuget包,可以轻松地为您完成各种常见任务。其中之一是发出HTTP请求。通常我总是使我的HTTP请求异步,但是在此Nuget包中是以下代码:

protected T GetRequest<T>(string requestUri)
{
// Call the async method within a task to run it synchronously
return Task.Run(() => GetRequestAsync<T>(requestUri)).Result;
}

调用此函数:

protected async Task<T> GetRequestAsync<T>(string requestUri)
{
// Set up the uri and the client
string uri = ParseUri(requestUri);
var client = ConfigureClient();
// Call the web api
var response = await client.GetAsync(uri);
// Process the response
return await ProcessResponse<T>(response);
}

我的问题是,这段代码真的通过包装GetRequestAsync(requestUri(在 Task.Run 中并调用来同步运行吗?返回任务的结果?这似乎是一个等待发生的死锁,我们在应用程序的区域看到了在较高负载下运行时使用此函数的问题。

访问Task.Result将阻塞当前线程,直到Task完成,因此它不是异步的。

至于死锁,这不应该发生,因为Task.Run使用另一个线程进行GetRequestAsync,该线程不会被调用Result阻止。

不会导致死锁的原因是 Task.Run 将推送要在线程池线程中执行的委托。线程池线程没有同步上下文,因此不会发生死锁,因为在异步方法 GetRequestAsync 和调用方之间没有要锁定的同步上下文。和你可以叫的一样.直接在实际的异步方法上以及在 Task.Run(( 块中的结果,这也不会导致死锁。

效率非常低,虽然当你冻结 1 个线程,即 CPU 中的 1 个内核,除了等待异步方法和其中的 I/O 调用完成之外什么都不做。这可能就是您在高负载情况下看到冻结的原因。

如果由于捕获同步上下文和阻止异步调用而出现同步/异步死锁问题,则无论单个调用的负载如何,都会发生死锁。

这不会导致死锁。但这肯定是一种资源浪费,因为其中一个线程可能会被阻塞。

如果GetRequest看起来像这样,则可能会出现僵局:

protected T GetRequest<T>(string requestUri)
{
var task = GetRequestAsync<T>(requestUri);
return task.Result;
// or
// return GetRequestAsync<T>(requestUri).Result;
}

在上面的例子中,你可以看到我在当前线程中调用GetRequestAsync。让我们给线程一个数字 0。考虑GetRequestAsync-await client.GetAsync(uri)中的这条线..GetAsync由线程 1 执行。完成后.GetAsync,默认任务计划程序将尝试将执行流返回到执行该行的线程 - 返回到线程 0。但是执行该行 (0( 的线程现在被阻塞,因为在我们执行GetRequestAsync()之后,我们正在用task.Result阻止它(线程 0(。因此,我们的线程 0 仍然被阻塞,因为它无法在完成后继续执行GetRequestAsyncawait client.GetAsync(uri)也无法给我们结果。

这是一个非常常见的错误,我想当被问及僵局时,你的意思是这个。您的代码不是导致此问题的,因为您正在从另一个线程中执行GetRequestAsync

最新更新