使用CancellationTokenSource时,NetworkStream ReadAsync和WriteAsync无限挂起-Task.Result(或Task.Wait)引起的死锁 [英] NetworkStream ReadAsync and WriteAsync hang infinitelly when using CancellationTokenSource - Deadlock Caused by Task.Result (or Task.Wait)

查看:185
本文介绍了使用CancellationTokenSource时,NetworkStream ReadAsync和WriteAsync无限挂起-Task.Result(或Task.Wait)引起的死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读了有关Stack Overflow和Microsoft有关NetworkStream的文档的几乎所有问题之后,我不明白我的代码有什么问题。

After reading pretty much every question on Stack Overflow and Microsoft's documentation about NetworkStream, I dont understand what is wrong with my code.

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

The problem I see is that my method GetDataAsync() hangs very often. I call this method from Init Method like so:

public MyView(string id)
{
    InitializeComponent();

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

上面,我的View进行了初始化,然后从Autofac DiC解析MyViewModel,然后调用MyViewModel Init()方法在VM上进行一些其他设置。

Above, my View does its initialization, then resolves MyViewModel from Autofac DiC and then calls MyViewModel Init() method to do some additional setup on the VM.

然后,Init方法调用我的Async方法GetDataAsync,该方法将返回一个IList,如下所示:

The Init method then calls my Async method GetDataAsync which return a IList like so:

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()方法,其中包括注释:

, and here is my GetDataAsync() method including my comments:

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。

I would expect that a ReadAsync or WriteAsync either complete successfully, throw some exception, or get cancelled after 10 seconds in which case I would catch OperationCanceledException.

但是,当我调用上述方法时,它会无限地挂起。如果我正在调试并且在上面的代码中有一些断点,则可以完全通过该方法,但是如果我第二次调用该方法,则应用程序将永远挂起。

我是Tasks和Async编程的新手,所以我也不确定我是否可以在这里正确进行取消和异常处理吗?

更新和修复

我想出了解决死锁问题的方法。希望这会帮助其他人遇到同样的问题,我先解释一下。

I figured out how to fix the deadlock issue. In hope this will help others sho might run into the same issue, I'll first explain it. The articles that helped me a lot are:

https://devblogs.microsoft.com/pfxteam/await-and-ui-and-deadlocks-oh-my/ 由Stephen Taub
https://montemagno.com/c-sharp-developers-stop -calling-dot-result / 由James Montemagno
https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx ,作者StephenCleary
https://blog.xamarin.com/getting-started-with-async-await/ 由Jon Goldberger

https://devblogs.microsoft.com/pfxteam/await-and-ui-and-deadlocks-oh-my/ by Stephen Taub https://montemagno.com/c-sharp-developers-stop-calling-dot-result/ by James Montemagno https://msdn.microsoft.com/en-us/magazine/jj991977.aspx by StephenCleary https://blog.xamarin.com/getting-started-with-async-await/ by Jon Goldberger

@StephenCleary对理解该问题很有帮助。调用 Result Wait (上面,我称 Result 为调用 GetDataAsync 将导致死锁

@StephenCleary was great help understanding the issue. Calling Result or Wait (above, I call Result when calling GetDataAsync) will lead to dead-lock.

上下文线程(在本例中为UI)正在等待 GetDataAsync 完成,但 GetDataAsync 捕获当前的上下文线程(UI线程),因此一旦它从TCP获取数据,它就可以在其上恢复。但是,由于此上下文线程现在已被调用 Result 阻止,因此无法恢复。

The context thread (UI in this case) is now waiting for GetDataAsync to complete, but GetDataAsync captures the current context-thread (UI thread), so it can resume on it once it gets data from TCP. But since this context-thread is now blocked by call to Result, it cannot resume.

最终结果是,对 GetDataAsync 的调用似乎已死锁,但实际上,它是对结果陷入僵局。

The end result is that it looks like call to GetDataAsync has deadlocked but in reality, it is call to Result that deadlocked.

在阅读了来自@ StephenTaub,@ StephenCleary,@ JamesMontemagno,@ JoeGoldenberger的大量文章之后(谢谢大家),我开始对此问题有所了解(我是TAP / async / await的新手)。

After reading tons of articles from @StephenTaub, @StephenCleary, @JamesMontemagno, @JoeGoldenberger (thank you all), I started getting understanding of the issue (I am new to TAP/async/await).

然后,我发现了Tasks的延续以及如何使用它们来解决问题(感谢Stephen Taub的上述文章)。

Then I discovered continuations in Tasks and how to use them to resolve the issue (thanks to Stephen Taub's article above).

所以,与其这样称呼:

IList<MyModel> models = GetDataAsync("111").Result;
foeach(var model in models)
{
  MyModelsObservableCollection.Add(model);
}

,我继续这样称呼它:

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 。因此,我也进行了尝试,并且也没有阻塞,因此效果很好:

So, this seam to work just fine. But @JoeGoldenberger also suggests another solution in his article https://blog.xamarin.com/getting-started-with-async-await/ which is to use Task.Run(async()=>{...}); and inside that await GetDataAsync and load ObservableCollection. So, I gave that a try as well and that is not blocking either, so working great:

Task.Run(async() =>  
{
    IList<MyModel> models = await GetDataAsync(id);
    foreach (var model in models)
    {
        MyModelsObservableCollection.Add(model);
    }
});

因此,看起来这两个中的任何一个都可以消除死锁。而且由于我的Init方法是从c-tor调用的;因此,我无法使其异步并等待它,使用上述2种方法之一可以解决我的问题。我不知道哪个更好,但是在我的测试中,它们确实可以工作。

So, it looks like either of these 2 will remove deadlock just fine. And since above my Init method is called from a c-tor; therefore, I cannot make it Async and await on this, using one of the 2 methods described above resolves my problem. I dont know which one is better but in my tests, they do work.

推荐答案

如我的更新所述,全部异步通过提供如下所示的异步lambda的方式为我解决了这个问题

As stated in my update, going async all the way by providing an async lambda like below resolved the issue for me

Task.Run(async() =>  
{
    IList<MyModel> models = await GetDataAsync(id);
    foreach (var model in models)
    {
        MyModelsObservableCollection.Add(model);
    }
});

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

Loading asynchronously an observable collection in a ctor this way (in my case, ctor calls Init which then uses this Task.Run) solves problem

这篇关于使用CancellationTokenSource时,NetworkStream ReadAsync和WriteAsync无限挂起-Task.Result(或Task.Wait)引起的死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆