Azure存储表:等待table.ExecuteAsync(InsertOperation)执行,但永远不会完成。

5
我的问题是,在Azure存储表上调用await table.ExecuteAsync(...)可以插入所请求的数据,但从未完成(不返回TableResult)。对于InsertOrUpdate和Update操作也是同样的情况。我还尝试了具有不同属性数量的不同表格 - 仍然存在相同的问题。
当我调用table.Execute(...)时 - 对于每种操作都能正常工作。
以下是我的代码 - 就这么简单:
外部调用(它位于MVC控制器中的异步操作中):
List<Task<ServiceResult<Boolean?>>> addPostTasks = new List<Task<Common.ServiceResult<bool?>>>();              
foreach (var userStream in userStreams)
{
    Task<ServiceResult<Boolean?>> addPostTask = postsStorageSvc.AddImagePost(...);
    postsAddImagePostTasks.Add(addPostTask);
}
Task.WaitAll(addPostTasks.ToArray());

调用的方法:

public async Task<ServiceResult<Boolean?>> AddImagePost(...)
{
ServiceResult<Boolean?> result = new ServiceResult<bool?>(null);
try
{
    PostTableEntity newPost = new PostTableEntity(streamId.ToString(), Guid.NewGuid().ToString(), creatorId, date, htmlText);              
    TableOperation insertOperation = TableOperation.Insert(newPost);
    //Following line never ends!
    TableResult tableResult = await this._storageTableBootstrapper.Table.ExecuteAsync(insertOperation);                
    //Following line works perfect - but is not ASYNC
    TableResult tableResult = this._storageTableBootstrapper.Table.Execute(insertOperation);
}
catch (Exception ex)
{
    result.Result = false;
    result.Errors.Add("AzurePostsStorageService Unexpected error: " + ex.Message);
}
return result;
}

你是如何调用这个方法的? - Yuval Itzchakov
你的意思是什么?这个代码块在异步方法中,我正在更新我的问题以显示周围的行。 - Krzysztof Rudnicki
展示方法签名以及如何调用它。你是否在使用Task.ResultTask.Wait时被阻塞了? - Yuval Itzchakov
我希望现在更清楚我是如何称呼它的。 - Krzysztof Rudnicki
3个回答

9
问题出在这里:
Task.WaitAll(addPostTasks.ToArray());

你的异步方法试图将自己马歇尔回到ASP.NET同步上下文中,但由于你使用了 Task.WaitAll 进行阻塞调用,因此同步上下文被卡住了。

相反,你需要遵循完全异步的模式,使用 Task.WhenAll,并在其上使用 await

await Task.WhenAll(addPostTasks.ToArray);

Stephan Cleary在他的博客文章中详细阐述了这一点(@NedStoyanov添加了这篇文章):

另一个重要的观点:ASP.NET请求上下文并不与特定线程绑定(就像UI上下文一样),但它确实只允许一次只有一个线程进入。据我所知,这个有趣的方面在官方文档中没有正式记录,但它在我的MSDN文章中提到过关于SynchronizationContext。


4
这是一个典型的死锁,由这行代码引起:

deadlock caused by this line:

Task.WaitAll(addPostTasks.ToArray());

试着将其改为:

await Task.WhenAll(addPostTasks.ToArray());

基本上,Task.WaitAll会阻塞请求线程,并且无法执行由await table.ExecuteAsync(...)发起的Tasks的继续操作。另一种选择是在内部任务中使用ConfigureAwait(false)来避免切换SynchronizatonContext
await table.ExecuteAsync(...).ConfigureAwait(false);

您可以在不需要切换到原始SynchronizationContext时使用ConfigureAwait(false)。在您的情况下,我相信您可以在所有等待操作中都这样做,因为您正在服务器上,如果await后面的代码在线程池上执行或不执行都没有关系。
请参阅此文章获取更多详情:msdn.microsoft.com/enus/magazine/jj991977.aspx

你的回答与上述答案意思相同,但是Yuval Itzchakov先提供了更多信息。 - Krzysztof Rudnicki
好的,为了更清晰地理解情况。您会建议在所有位于执行堆栈顶部的异步操作(如table.ExecuteAsync(...)或我们自己的异步操作/方法)中使用".ConfigureAwait(false)"吗? - Krzysztof Rudnicki
除非我在GUI线程中并且在await之后访问GUI元素,否则我会这样做。有关更多详细信息,请参阅此文章:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx - NeddySpaghetti

0
如果您在为组织工作,问题可能出在组织启用的代理设置上。我曾经遇到过类似的问题,解决方法如下:
1. 在startup.cs中添加以下代码:
app.UseDeveloperExceptionPage();
                var webProxy = new WebProxy(new Uri(Configuration["defaultProxy:proxyaddress"]), BypassOnLocal: false);
                var proxyHttpClientHandler = new HttpClientHandler
                {
                    Proxy = webProxy,
                    UseProxy = true,
                };
                AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);

2:在appsettings.json文件中添加

"defaultProxy": {
    "proxyaddress": "<your IP here>"
  },

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接