在阅读了有关Stack Overflow和Microsoft有关NetworkStream的文档的几乎所有问题之后,我不明白我的代码有什么问题。
我看到的问题是我的方法GetDataAsync()经常挂起。我从Init方法调用此方法,如下所示:
public MyView(string id)
{
InitializeComponent();
MyViewModel myViewModel = session.Resolve<MyViewModel>(); //Autofac
myiewModel.Init(id);
BindingContext = myViewModel;
}
上面,我的View进行了初始化,然后从Autofac DiC解析MyViewModel,然后调用MyViewModel Init()方法在VM上进行一些附加设置。
然后,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。
但是,当我调用上面的方法时,它只是无限地挂起。如果我正在调试并且上面的代码中有一些断点,则可以完全通过该方法,但是如果我第二次调用该方法,则应用程序将永远挂起。
我是Tasks和Async编程的新手,所以我也不确定在这里是否正确执行取消和异常处理吗?
更新和修复
我想出了解决死锁问题的方法。希望这会帮助其他人遇到同样的问题,我先解释一下。对我有很大帮助的文章是:
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/en-us/magazine/jj991977.aspx,作者:StephenCleary https://blog.xamarin.com/getting-started-with-async-await/,作者:乔恩·戈德堡(Jon Goldberger)
@StephenCleary对理解该问题很有帮助。呼叫Result
或Wait
(上面,我在呼叫Result
时呼叫GetDataAsync
)会导致死锁。
上下文线程(在这种情况下为UI)现在正在等待GetDataAsync
完成,但是GetDataAsync
捕获了当前上下文线程(UI线程),因此一旦获得,它就可以在其上继续来自TCP的数据。但是,由于此上下文线程现在已被调用Result
阻塞,因此无法恢复。
最终结果是,对GetDataAsync
的调用似乎已死锁,但实际上,对Result
的调用却已死锁。
在阅读了来自@ StephenTaub,@ StephenCleary,@ JamesMontemagno,@ JoeGoldenberger的大量文章之后(谢谢大家),我开始了解这个问题(我是TAP / async / await的新手)。
然后,我发现了Tasks中的续篇以及如何使用它们来解决问题(感谢Stephen Taub的上述文章)。
所以,与其像这样称呼它:
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);
}
});
因此,看起来这2个都可以消除死锁。而且由于我的Init方法是从c-tor调用的;因此,我无法使其异步并等待它,使用上述2种方法之一可以解决我的问题。我不知道哪个更好,但是在我的测试中,它们确实有效。
答案 0 :(得分:2)
您的问题很可能是由于GetDataAsync("111").Result
引起的。您shouldn't block on async
code。
这可能会导致死机。例如,如果您使用的是UI线程,则UI线程将启动GetDataAsync
并运行它,直到遇到await
。此时,GetDataAsync
返回一个未完成的任务,并且.Result
调用将阻塞UI线程,直到该任务完成。
最终,内部异步调用完成,GetDataAsync
准备在其await
之后继续执行。默认情况下,await
捕获其上下文并在该上下文上恢复。在此示例中,哪个是UI线程。由于它叫Result
,因此已被阻止。因此,UI线程正在等待GetDataAsync
完成,而GetDataAsync
正在等待UI线程可以完成:死锁。
正确的解决方法是转到async all the way;将.Result
替换为await
,然后对其他代码进行必要的更改。
答案 1 :(得分:0)
如我的更新所述,通过提供如下所示的异步lambda来实现全程异步为我解决了问题
Task.Run(async() =>
{
IList<MyModel> models = await GetDataAsync(id);
foreach (var model in models)
{
MyModelsObservableCollection.Add(model);
}
});
以这种方式在ctor中异步加载可观察的集合(在我的情况下,ctor调用Init,然后使用此Task.Run)解决了问题