首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NetworkStream ReadAsync和WriteAsync在使用由Task.Result (或Task.Wait)引起的CancellationTokenSource死锁时无限挂起

NetworkStream ReadAsync和WriteAsync在使用由Task.Result (或Task.Wait)引起的CancellationTokenSource死锁时无限挂起
EN

Stack Overflow用户
提问于 2019-02-26 19:17:00
回答 2查看 1.3K关注 0票数 1

在阅读了几乎所有关于堆栈溢出的问题和微软关于NetworkStream的文档之后,我不明白我的代码有什么问题。

我看到的问题是,我的方法GetDataAsync()经常挂起。我从Init方法调用这个方法,如下所示:

代码语言:javascript
复制
public MyView(string id)
{
    InitializeComponent();

    MyViewModel myViewModel = session.Resolve<MyViewModel>(); //Autofac
    myiewModel.Init(id);
    BindingContext = myViewModel;
}

上面,我的视图完成了它的初始化,然后从Autofac DiC解析DiC,然后调用MyViewModel Init()方法在VM上做一些额外的设置。

然后,Init方法调用我的异步方法GetDataAsync,该方法返回如下所示的IList:

代码语言:javascript
复制
public void Init()
{
    // call this Async method to populate a ListView
    foreach (var model in GetDataAsync("111").Result)
    {
        // The List<MyModel> returned by the GetDataAsync is then
        // used to load ListView's ObservableCollection<MyModel>
        // This ObservableCollection is data-bound to a ListView in
        // this View.  So, the ListView shows its data once the View
        // displays.
    }
}

,下面是我的GetDataAsync()方法,包括我的注释:

代码语言:javascript
复制
public override async Task<IList<MyModel>> GetDataAsync(string id)
{
    var timeout = TimeSpan.FromSeconds(20);

    try
    {
        byte[] messageBytes = GetMessageBytes(Id);

        using (var cts = new CancellationTokenSource(timeout))
        using (TcpClient client = new TcpClient(Ip, Port))
        using (NetworkStream stream = client.GetStream())
        {
            await stream.WriteAsync(messageBytes, 0, messageBytes.Length, cts.Token);
            await stream.FlushAsync(cts.Token);

            byte[] buffer = new byte[1024];
            StringBuilder builder = new StringBuilder();
            int bytesRead = 0;

            await Task.Delay(500);                 
            while (stream.DataAvailable) // need to Delay to wait for data to be available
            {
                bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
                builder.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, bytesRead));
            }

            string msg = buffer.ToString();
        }

        return ParseMessageIntoList(msg);  // parses message into IList<MyModel>
    }
    catch (OperationCanceledException oce)
    {
        return await Task.FromResult<IList<RoomGuestModel>>(new List<RoomGuestModel>());
    }
    catch (Exception ex)
    {
        return await Task.FromResult<IList<RoomGuestModel>>(new List<RoomGuestModel>());
    }
}

我希望ReadAsync或WriteAsync可以成功完成、抛出一些异常,或者在10秒后被取消,在这种情况下,我将捕获OperationCanceledException。

然而,当我调用上面的方法时,它就会无休止地挂起。如果我正在调试,并在上面的代码中有一些断点,我将能够完全通过这个方法,但是如果我第二次调用它,应用程序就会永远挂起。。

我对任务和异步编程并不熟悉,所以我也不确定我是否在这里正确地执行了取消和异常处理??

更新和修复

我想出了解决僵局问题的方法。希望这将有助于其他人可能会遇到同样的问题,我将首先解释。对我帮助很大的文章有:

https://devblogs.microsoft.com/pfxteam/await-and-ui-and-deadlocks-oh-my/”,斯蒂芬·陶布,https://montemagno.com/c-sharp-developers-stop-calling-dot-result/,詹姆斯·蒙塔玛诺,https://msdn.microsoft.com/en-us/magazine/jj991977.aspx,StephenCleary https://blog.xamarin.com/getting-started-with-async-await/,乔恩·戈德伯格

@StephenCleary非常有助于理解这个问题。调用ResultWait (我在调用GetDataAsync时调用Result )将导致死锁

上下文线程(在本例中是UI)正在等待GetDataAsync完成,但是GetDataAsync捕获了当前的上下文线程(UI线程),因此一旦从TCP获得数据,它就可以继续运行。但是,由于这个上下文线程现在被调用Result所阻塞,所以它无法恢复。

最终的结果是,对GetDataAsync的调用似乎已经陷入僵局,但实际上,对Result的调用却陷入了僵局。

在阅读了大量来自“StephenTaub”、@StephenCleary、@JamesMontemagno、@JoeGoldenberger (谢谢大家)的文章之后,我开始了解这个问题(我是新来的挖掘/异步/等待)。

然后,我发现了任务中的连续性,以及如何使用它们来解决问题(感谢Stephen上面的文章)。

因此,与其称它为:

代码语言:javascript
复制
IList<MyModel> models = GetDataAsync("111").Result;
foeach(var model in models)
{
  MyModelsObservableCollection.Add(model);
}

,我这样称呼它为延续:

代码语言:javascript
复制
GetDataAsync(id)
    .ContinueWith((antecedant) =>
    {
        foreach(var model in antecedant.Result)
        {
            MyModelsObservableCollection.Add(model);
        }

    }, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith((antecedant) =>
    {
        var error = antecedant.Exception.Flatten();
    }, TaskContinuationOptions.OnlyOnFaulted);

This seam to have fixed my deadlocking issue and now my list will load fine even though it is loaded from the constructor.  

所以,这条缝很好用。但@JoeGoldenberger在他的文章“https://blog.xamarin.com/getting-started-with-async-await/”中也提出了另一个解决方案,即使用Task.Run(async()=>{...});并在其内部等待GetDataAsync和加载ObservableCollection。所以,我也试了一试,但这也没有阻挡,所以效果很好:

代码语言:javascript
复制
Task.Run(async() =>  
{
    IList<MyModel> models = await GetDataAsync(id);
    foreach (var model in models)
    {
        MyModelsObservableCollection.Add(model);
    }
});

因此,看起来这两种方法中的任何一种都能很好地消除死锁。由于上面的Init方法是从c-tor调用的;因此,我不能使用上面描述的两种方法中的一种来异步并等待它来解决我的问题。我不知道哪一个更好,但在我的测试中,它们确实有效。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-04-25 23:53:22

正如我在更新中所述,通过提供如下所示的异步lambda,一路走异步解决了我的问题。

代码语言:javascript
复制
Task.Run(async() =>  
{
    IList<MyModel> models = await GetDataAsync(id);
    foreach (var model in models)
    {
        MyModelsObservableCollection.Add(model);
    }
});

以这种方式异步地在ctor中加载一个可观察的集合(在我的例子中,ctor调用Init,然后使用此Task.Run)解决了问题

票数 0
EN

Stack Overflow用户

发布于 2019-02-27 04:58:37

您的问题很可能是由于GetDataAsync("111").Result造成的。你代码

这可能会造成死亡。例如,如果您在UI线程上,UI线程将启动GetDataAsync并运行它,直到它到达一个await为止。此时,GetDataAsync返回一个不完整的任务,.Result调用阻塞UI线程,直到该任务完成。

最终,内部异步调用完成,GetDataAsync准备在其await之后恢复执行。默认情况下,await捕获其上下文并在该上下文上继续。在本例中,它是UI线程。它被阻塞,因为它被称为Result。因此,UI线程正在等待GetDataAsync完成,而GetDataAsync正在等待UI线程,以便它能够完成:死锁。

正确的解决方案是转到一路异步;将.Result替换为await,并对其他代码进行必要的更改。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54892579

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档