使用下面的代码,最终的ContinueWith中的最终UI更新永远不会发生。我认为这是因为我最后有Wait()。
我这样做的原因是因为没有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线程交谈时遇到麻烦。第一个继续更新ui就好了。我遇到的麻烦是最后一次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);
}
答案 0 :(得分:3)
以下更新
<击>
此代码看起来不像我向TPL保证。看起来可能很适合用于BackgroundWorker!
无论哪种方式,更新都可能没有发生,因为您无法从单独的线程更新UI - 您需要在UI线程上运行更新。您应该使用Dispatcher(http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms包含WPF和WinForms的信息)
击>
的更新强>
所以我显然错过了一些代码,所以这里是修改后的答案。首先,Nicholas是正确的 - 。继续返回一个新任务(http://msdn.microsoft.com/en-us/library/dd270696.aspx)。而不是
var result = Task.Factory.StartNew(...);
result.ContinueWith(...);
您可能想创建一个新任务,然后进行所有ContinueWith()
次调用并分配给该任务,然后在该任务上调用.Start()
。类似的东西:
var task = new Task(...).ContinueWith(...);
task.Start();
然而,设计中存在一个缺陷(我认为)!您正在尝试运行此代码异步,这就是您使用线程和TPL的原因。但是,您在UI线程上调用buildProvider.Wait();
来阻止UI线程,直到此任务完成为止!除了在UI线程被阻止的情况下重新绘制ContinueWith()
中的UI的问题之外,这里的多线程没有任何好处,因为你阻止了UI线程(一个主要的禁忌)。您可能想要做的是将Bind()
- 添加到ContinueWith或其他内容中,这样您就不必调用Wait()
并阻止UI线程。
我的0.02美元是如果您希望查询花费很长时间,您真正想要的是2个线程(或TPL中的任务) - 一个用于执行查询,另一个用于以状态间隔更新UI。如果您不希望它花费这么长时间,我认为您只需要一个线程(Task)进行查询,然后在完成后更新UI。我可能会通过BackgroundWorker
来做这件事。 TPL是为管理大量任务和延续而构建的,但对于这类事情来说似乎有些过分 - 我认为你可以使用很少代码的BackgroundWorker来完成它。但是你提到你想要使用TPL,这很好,但你将不得不重做这一点,以便它实际上在后台运行!
PS - 您可能打算将Console.WriteLine("Exception handled. Let's move on.");
放在catch
答案 1 :(得分:2)
我有点朦胧,但上次我使用TPL时发现它令人困惑。 ContinueWith()
会返回一个新的Task
实例。因此,您需要将第二个ContinueWith()
结果分配给新变量,例如var continuedTask = builderProvider.ContinueWith(...)
,然后将最后一个更改为引用continuedTask.ContinueWith()
而不是buildProvider.ContinueWith()
。然后是Wait()
上的Task
。
希望有所帮助!