任务工作流程顺序错误
使用下面的代码,在最终的ContinueWith中进行的最终UI更新永远不会发生。我认为这是因为我最后有 Wait() 。
我这样做的原因是因为如果没有等待,该方法将在 IDataProvider 在后台完成构建之前返回它。
有人可以帮我解决这个问题吗?
干杯,
Berryl
private IDataProvider _buildSQLiteProvider()
{
IDataProvider resultingDataProvider = null;
ISession session = null;
var watch = Stopwatch.StartNew();
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
// get the data
var buildProvider = Task.Factory
.StartNew(
() =>
{
// code to build it
});
// show some progress if we haven't finished
buildProvider.ContinueWith(
taskResult =>
{
// show we are making progress;
},
CancellationToken.None, TaskContinuationOptions.None, uiContext);
// we have data: reflect completed status in ui
buildProvider.ContinueWith(
dataProvider =>
{
// show we are finished;
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext);
try {
buildProvider.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
CurrentSessionContext.Bind(session);
return resultingDataProvider;
}
====
只是为了清楚起见,
我在与 ui 线程交谈时没有遇到问题。第一个继续更新用户界面就好了。我遇到的麻烦是最后一次用户界面更新的时间和数据提供者的返回。
我注释掉了一些代码以降低本文中的噪音水平并专注于任务排序。
====
好的,工作代码
private void _showSQLiteProjecPicker()
{
var watch = Stopwatch.StartNew();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ISession session = null;
// get the data
var buildProvider = Task.Factory.StartNew(
() =>
{
var setProgress = Task.Factory.StartNew(
() =>
{
IsBusy = true;
Status = string.Format("Fetching data...");
},
CancellationToken.None, TaskCreationOptions.None, uiScheduler);
var provider = new SQLiteDataProvider();
session = SQLiteDataProvider.Session;
return provider;
});
buildProvider.ContinueWith(
buildTask =>
{
if(buildTask.Exception != null) {
Console.WriteLine(buildTask.Exception);
}
else {
Check.RequireNotNull(buildTask.Result);
Check.RequireNotNull(session);
_updateUiTaskIsComplete(watch);
CurrentSessionContext.Bind(session);
var provider = buildTask.Result;
var dao = provider.GetActivitySubjectDao();
var vm = new ProjectPickerViewModel(dao);
_showPicker(vm);
}
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
}
With the code below, the final UI updates made in the final ContinueWith never take place. I think it is because of the Wait() I have at the end.
The reason I am doing that is because without the Wait, the method will return the IDataProvider before its finished being constructed in the background.
Can someone help me get this right?
Cheers,
Berryl
private IDataProvider _buildSQLiteProvider()
{
IDataProvider resultingDataProvider = null;
ISession session = null;
var watch = Stopwatch.StartNew();
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
// get the data
var buildProvider = Task.Factory
.StartNew(
() =>
{
// code to build it
});
// show some progress if we haven't finished
buildProvider.ContinueWith(
taskResult =>
{
// show we are making progress;
},
CancellationToken.None, TaskContinuationOptions.None, uiContext);
// we have data: reflect completed status in ui
buildProvider.ContinueWith(
dataProvider =>
{
// show we are finished;
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext);
try {
buildProvider.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
CurrentSessionContext.Bind(session);
return resultingDataProvider;
}
====
just to be clear
I am not having trouble talking to the ui thread. The first continue with updates the ui just fine. The trouble I am having is the timing of the last ui update and the return of the data provider.
I commented out some of the code to reduce the noise level in tis post and focus on the task sequencing.
====
ok, working code
private void _showSQLiteProjecPicker()
{
var watch = Stopwatch.StartNew();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ISession session = null;
// get the data
var buildProvider = Task.Factory.StartNew(
() =>
{
var setProgress = Task.Factory.StartNew(
() =>
{
IsBusy = true;
Status = string.Format("Fetching data...");
},
CancellationToken.None, TaskCreationOptions.None, uiScheduler);
var provider = new SQLiteDataProvider();
session = SQLiteDataProvider.Session;
return provider;
});
buildProvider.ContinueWith(
buildTask =>
{
if(buildTask.Exception != null) {
Console.WriteLine(buildTask.Exception);
}
else {
Check.RequireNotNull(buildTask.Result);
Check.RequireNotNull(session);
_updateUiTaskIsComplete(watch);
CurrentSessionContext.Bind(session);
var provider = buildTask.Result;
var dao = provider.GetActivitySubjectDao();
var vm = new ProjectPickerViewModel(dao);
_showPicker(vm);
}
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
更新如下
<罢工>
对我来说,这段代码看起来并不值得 TPL。看起来也许使用 BackgroundWorker 是一个很好的选择!
无论哪种方式,更新都可能不会发生,因为您无法从单独的线程更新 UI - 您需要在用户界面线程。您应该为此使用调度程序(http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms 包含 WPF 和 WinForms 的信息)
更新:所以我显然错过了一些代码,所以这里有一个修改后的答案。首先,尼古拉斯是正确的 - .ContinueWith 返回一个新任务(http://msdn.microsoft.com/en-us/library/dd270696.aspx)。因此,
您可能想要创建一个新任务,然后进行所有
ContinueWith()
调用并分配给该任务,然后对该任务调用.Start()
。比如:然而,设计一开始就存在缺陷(据我所知)!您尝试异步运行此代码,这就是您使用线程和 TPL 的原因。但是,您在 UI 线程上调用
buildProvider.Wait();
会阻塞 UI 线程,直到此任务完成!除了在 UI 线程被阻塞时在ContinueWith()
中重新绘制 UI 的问题之外,这里的多线程没有任何好处,因为您阻塞了 UI 线程(这是一个主要的禁忌)。您可能想要做的是将Bind()
-ing 粘贴到 ContinueWith 或其他内容中,这样您就不必调用Wait()
并阻止 UI 线程。我的 0.02 美元是,如果您预计查询需要很长时间,那么您真正想要的是 2 个线程(或 TPL 中的任务)——一个用于执行查询,另一个用于定期更新 UI 状态。如果您不希望花费这么长时间,我认为您只需要一个线程(任务)来查询,然后在完成后更新 UI。我可能会通过
BackgroundWorker
来完成此操作。 TPL 是为管理大量任务和延续等而构建的,但对于此类事情似乎有点过分了——我认为您可以使用后台工作程序以更少的代码来完成它。但你提到你想使用 TPL,这很好,但你必须稍微修改一下,以便它实际上在后台运行!PS - 您可能打算将
Console.WriteLine("Exception Handling. Let's move on."); 放在
catch
内UPDATE BELOW
This code doesn't look like it warrants TPL to me. Looks like maybe a good use for a BackgroundWorker instead!
Either way, the updates are probably not taking place because you can't update the UI from a separate thread -- you need to run the update on the UI thread. You should use the Dispatcher for this (http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms contains info for both WPF and WinForms)
Update:
So I obviously missed some of the code so here's a revised answer. First of all, Nicholas is correct -- .ContinueWith returns a new task (http://msdn.microsoft.com/en-us/library/dd270696.aspx). So instead of
you probably want to create a new task and then make all the
ContinueWith()
calls and assign to the task and then call.Start()
on the task. Something like:However, there is a flaw in the design to begin with (as I see it)! You're trying to run this code async, wihch is why you're using threads and TPL. However, you're calling
buildProvider.Wait();
on the UI thread which blocks the UI thread until this task completes! Aside from the issue of repainting the UI in theContinueWith()
while the UI thread is blocked, there's no benefit to multithreading here since you're blocking the UI thread (a major no-no). What you probably want to do is stick theBind()
-ing inside a ContinueWith or something so that you don't have to callWait()
and block the UI thread.My $0.02 is that if you expect the query to take a long time what you really want is 2 threads (or tasks in TPL)-- one to perform the query and one to update the UI at intervals with status. If you don't expect it to take so long I think you just want a single thread (Task) to query and then update the UI when it's done. I would probably do this via
BackgroundWorker
. TPL was built for managing lots of tasks and continuations and such but seems overkill for this kind of thing -- I think you could do it using a BackgroundWorker in a lot less code. But you mention you want to use TPL which is fine, but you're going to have to rework this a bit so that it actually runs in the background!PS - you probably meant to put the
Console.WriteLine("Exception handled. Let's move on.");
inside thecatch
我有点模糊,但上次我使用 TPL 时我发现它很混乱。
ContinueWith()
返回一个新的Task
实例。因此,您需要将第二个ContinueWith()
结果分配给一个新变量,例如var ContinuousTask = builderProvider.ContinueWith(...)
,然后将最后一个更改为参考continuedTask.ContinueWith()
而不是buildProvider.ContinueWith()
。然后在最后一个Task
上Wait()
。希望有帮助!
I'm a little hazy, but last time I used the TPL I found it confusing.
ContinueWith()
returns a newTask
instance. So you need to assign the secondContinueWith()
result to a new variable, sayvar continuedTask = builderProvider.ContinueWith(...)
, and then change the last one to referencecontinuedTask.ContinueWith()
instead ofbuildProvider.ContinueWith()
. ThenWait()
on the lastTask
.Hope that helps!