После прочтения почти каждого вопроса о переполнении стека и документации 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 методов, описанных выше, чтобы решить мою проблему.Я не знаю, какой из них лучше, но в моих тестах они работают.