NetworkStream ReadAsync и WriteAsync бесконечно зависают при использовании CancellationTokenSource - тупик, вызванный Task.Result (или Task.Wait) - PullRequest
0 голосов
/ 26 февраля 2019

После прочтения почти каждого вопроса о переполнении стека и документации Microsoft о NetworkStream я не понимаю, что не так с моим кодом.

Проблема, которую я вижу, заключается в том, что мой метод GetDataAsync () очень часто зависает.Я вызываю этот метод из метода Init следующим образом:

public MyView(string id)
{
    InitializeComponent();

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

Выше мой View выполняет свою инициализацию, затем разрешает MyViewModel из Autofac DiC и затем вызывает метод MyViewModel Init (), чтобы выполнить некоторые дополнительные настройки на виртуальной машине.

Затем метод Init вызывает мой метод Async GetDataAsync, который возвращает IList примерно так:

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 (), включающий мои комментарии:

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 Стивена Клири https://blog.xamarin.com/getting-started-with-async-await/ Джона Голдбергера

@ StephenCleary помогли понять проблему.Вызов Result или Wait (выше, я звоню Result при вызове GetDataAsync) приведет к тупиковой блокировке .

Поток контекста (в данном случае пользовательский интерфейс) теперь ожидает завершения GetDataAsync, но GetDataAsync захватывает текущий поток контекста (поток пользовательского интерфейса), поэтому он может возобновить его после получения данных.из ПТС.Но так как этот поток контекста теперь заблокирован вызовом Result, он не может возобновиться.

В результате получается, что вызов GetDataAsync заблокирован, но на самом деле это вызов Result, который заблокирован.

После прочтения множества статей из @StephenTaub,@StephenCleary, @JamesMontemagno, @JoeGoldenberger (спасибо всем), я начал понимать проблему (я новичок в TAP / async / await).

Затем я обнаружил продолжения в Задачах и как их использовать для решения проблемы (благодаря статье Стивена Тауба выше).

Итак, вместо того, чтобы называть это как:

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.Итак, я также попробовал, и это тоже не блокирует, так что отлично работает:

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

Итак, похоже, что любой из этих двух вариантов удалит взаимоблокировку просто отлично.И так как выше мой метод Init вызывается из c-tor;поэтому я не могу сделать это асинхронно и ждать этого, используя один из 2 методов, описанных выше, чтобы решить мою проблему.Я не знаю, какой из них лучше, но в моих тестах они работают.

Ответы [ 2 ]

0 голосов
/ 26 апреля 2019

Как указано в моем обновлении, асинхронная лямбда, как показано ниже, решила проблему для меня

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

Асинхронная загрузка наблюдаемой коллекции в ctor таким образом (в моем случае,ctor вызывает Init, который затем использует этот Task.Run) решает проблему

0 голосов
/ 27 февраля 2019

Ваша проблема, скорее всего, связана с GetDataAsync("111").Result.Вы не должны блокировать код async .

. Это может вызвать взаимные блокировки.Например, если вы находитесь в потоке пользовательского интерфейса, поток пользовательского интерфейса запустит GetDataAsync и будет работать до тех пор, пока не достигнет await.На этом этапе GetDataAsync возвращает незавершенную задачу, а вызов .Result блокирует поток пользовательского интерфейса до тех пор, пока эта задача не будет завершена.

В конце концов внутренний асинхронный вызов завершается, и GetDataAsync готов возобновить выполнениепосле его await.По умолчанию await фиксирует свой контекст и возобновляет работу в этом контексте.Который в этом примере является потоком пользовательского интерфейса.Который заблокирован, так как он называется Result.Итак, поток пользовательского интерфейса ожидает завершения GetDataAsync, а GetDataAsync ожидает потока пользовательского интерфейса, чтобы он мог его завершить: deadlock.

Правильное решение - выполнить асинхронную работу полностью;замените .Result на await и внесите необходимые изменения в другой код, чтобы это произошло.

...