Последовательность рабочего процесса задач неверна
-
25-09-2019 - |
Вопрос
С приведенным ниже кодом окончательные обновления пользовательских интерфейсов, сделанные в финале, никогда не проводится. Я думаю, что это из-за ожидания () у меня есть в конце.
Причина, по которой я делаю это потому, что без ожидания метод вернет IDatataProvider до того, как его закончен, построенный на заднем плане.
Может кто-нибудь помочь мне получить это правильно?
Ваше здоровье,
Ягода
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 и возврата поставщика данных.
Я прокомментировал некоторые из кода, чтобы уменьшить уровень шума в TIS POST и сосредоточиться на секвенировке задач.
====
ОК, рабочий код
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);
}
Решение
Обновление ниже
Этот код не выглядит так, как правило, для меня TPL. Похоже, может быть, хорошее использование для фонара вместо этого!
В любом случае, обновления, вероятно, не имеют место, потому что вы не можете обновлять пользовательский интерфейс из отдельного потока - вам нужно запустить обновление в потоке пользовательского интерфейса. Вы должны использовать диспетчеров для этого (http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms содержит информацию как для WPF, так и для WinForms)
Обновлять:
Поэтому я, очевидно, упустил какой-то код, так что вот пересмотренный ответ. Прежде всего, Николас правильно - .Continuewith возвращает новую задачу (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();
Тем не менее, есть недостаток в дизайне, чтобы начать с (как я вижу это)! Вы пытаетесь запустить этот код Async, Wihch - это почему вы используете потоки и TPL. Тем не менее, вы звоните buildProvider.Wait();
На ui поток, которая блокирует нить UI, пока эта задача не завершится! Кроме вопроса о перекраске пользовательского интерфейса в ContinueWith()
Хотя нить пользовательского интерфейса заблокирована, здесь нет никакой пользы для многопоточничества здесь, так как вы блокируете нить UI (основной нет-нет). Что вы, вероятно, хотите сделать, это придерживаться Bind()
-Ну внутри продолжения или что-то, чтобы вам не нужно звонить Wait()
и заблокировать нить интерфейса.
Мои $ 0,02 - это то, что если вы ожидаете, что запрос займет много времени, что вы действительно хотите, это 2 потока (или задачи в TPL) - один для выполнения запроса и один, чтобы обновить интервалы UI с помощью статуса. Если вы не ожидаете, что это займет так много времени, я думаю, что вы просто хотите, чтобы выиграть одну нить (задачу), чтобы запросить, а затем обновить пользовательский интерфейс, когда это сделано. Я бы, вероятно, сделал это через BackgroundWorker
. Отказ TPL был построен для управления множеством задач и продолжений и такими, но кажется излишним для такого рода вещей - я думаю, что вы могли бы сделать это, используя фоновый механизм в гораздо меньший код. Но вы упомянуете, что хотите использовать TPL, что в порядке, но вам будет немного переделать это немного, чтобы он на самом деле работает на заднем плане!
PS - ты, вероятно, хотел поставить Console.WriteLine("Exception handled. Let's move on.");
внутри catch
Другие советы
Я немного туманно, но в прошлый раз я использовал TPL, я нашел с толку. ContinueWith()
Возвращает новый Task
пример. Так что вам нужно назначить второй ContinueWith()
привести к новой переменной, скажем var continuedTask = builderProvider.ContinueWith(...)
, и затем измените последний, чтобы ссылаться continuedTask.ContinueWith()
вместо buildProvider.ContinueWith()
. Отказ потом Wait()
в последнее время Task
.
Надеюсь, это поможет!