NetworkStream ReadAsync and WriteAsync hang infinitelly when using CancellationTokenSource - Deadlock Caused by Task.Result (or Task.Wait)

烂漫一生 提交于 2020-01-06 08:07:29

问题


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

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;
}

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.

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.
    }
}

, 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>());
    }
}

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.

However, it just hangs endlessly when I call method above. If I am debugging and have some breakpoints in the code above, I will be able to go through the method entirely but if I call it 2nd time, app just hangs forever.

I am new to Tasks and Async programming, so I am also not sure I do my cancellations and exception handling properly here?

UPDATE AND FIX

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/ 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 was great help understanding the issue. Calling Result or Wait (above, I call Result when calling GetDataAsync) will lead to dead-lock.

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.

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

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).

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

So, instead of calling it like:

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

, I call it with continuation like this:

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.  

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);
    }
});

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.


回答1:


Your problem is most likely due to GetDataAsync("111").Result. You shouldn't block on async code.

This can cause deadocks. E.g., if you're on a UI thread, the UI thread will start GetDataAsync and run it until it hits an await. At this point, GetDataAsync returns an incomplete task, and the .Result call blocks the UI thread until that task is completed.

Eventually, the inner async call completes and GetDataAsync is ready to resume executing after its await. By default, await captures its context and resumes on that context. Which in this example is the UI thread. Which is blocked since it called Result. So, the UI thread is waiting for GetDataAsync to complete, and GetDataAsync is waiting for the UI thread so it can complete: deadlock.

The proper solution is to go async all the way; replace .Result with await, and make the necessary changes to other code for that to happen.




回答2:


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);
    }
});

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



来源:https://stackoverflow.com/questions/54892579/networkstream-readasync-and-writeasync-hang-infinitelly-when-using-cancellationt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!