2013-11-18 5 views
3

В представлении-модели я использую завод:Асинхронных-Await, кажется, использует поток пользовательского интерфейса

private async Task<BaseData> InitializeAsync() 
{ 
    await InstancesAsync(); 
    await ProjectsAsync(); 
    await AdminAsync(); 
    return this; 
} 
public static async Task<BaseData> CreateAsync() 
{ 
    var ret = new BaseData(); 
    return await ret.InitializeAsync(); 
} 

Долгожданные методы довольно staightforward, например, с

var instances = await TaskEx.Run(new Func<List<string>>(() => Agent.GetInstances())); 

По мнению МОФ Я хочу, чтобы установить DataContext в конструкторе:

Loaded += delegate 
{ 
    Dispatcher.Invoke(new Action(async() => { DataContext = await BasisGegevens.CreateAsync(); })); 
}; 

Хотя это работает, я чувствую себя довольно неудобно, потому что поток пользовательского интерфейса используется везде, а также после того, как обратные вызовы когда ожидание завершено. Что мне не хватает?

Также я не понимаю, как использовать фабричный шаблон для DataContext, потому что без Invoke выше я получаю сообщение об ошибке, что другой объект принадлежит этому объекту.

EDIT: используя идеи г-Клири я получаю:

Loaded += async (object sender, RoutedEventArgs e) => 
      { DataContext = await BaseData.CreateAsync(); }; 
public static Task<BaseData> CreateAsync() 
{ 
    var ret = new BaseData(); 
    return ret.InitializeAsync(); 
} 
private async Task<BaseData> InitializeAsync() 
{ 
    // UI thread here 
    await InstancesAsync().ConfigureAwait(false); 
    // thread 'a' here 
    await ProjectsAsync().ConfigureAwait(false); 
    // thread 'a' sometimes 'b' here 
    await AdminAsync().ConfigureAwait(false); 
    // thread 'a' or 'b' here 
    return this; 
} 

Это работает отлично, за исключением того, что я не могу понять, как ConfigureAwait(false) работы.
Внутри метода InstancesAsync() я имею ждали задания:
var instances = await TaskEx.Run(new Func<List<string>>(() => Agent.GetInstances()));
После ожидания repsonse, я возвращаюсь в потоке пользовательского интерфейса - Я никогда не ожидал, что произойдет!
Обратите внимание, что ProjectsAsync() и AdminAsync() ведут себя одинаково, хотя они начинаются с рабочего (или заднего) потока!
Я понял, что ConfigureAwait(true) имеет эффект возврата в вызывающем потоке (в моем случае поток пользовательского интерфейса). Я тестировал это, и это так.
Почему я тоже вижу это с ConfigureAwait(false): из-за вложенного ожидания см. Комментарии.

+0

Что является источником события «Загружено» в коде, который вы показали? Если это регулярное событие ['FrameworkElement.Loaded'] (http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.loaded (v = vs.110) .aspx), оно должно быть запущен в потоке пользовательского интерфейса. Вы создаете представление для отдельного потока? Возможно, это потому, что вы вызываете «Агент».GetInstances() 'в потоке пула non-UI? – Noseratio

+0

Событие Loaded является обычным событием FrameworkElement.Loaded. Я намеревался создать представление в потоке пользовательского интерфейса, которое оказалось ошибочным. Agent.GetInstances() запускается в ожидаемой задаче, я ожидал, что это будет поток потока, отличного от UI, но это не так. – Gerard

+0

'TaskEx.Run' использует отдельный поток пулов для запуска вашего делегата' Func', в котором выполняется 'Agent.GetInstances()'. Я бы посоветовал вам следовать за ответом Стивена и убедиться, что оба элемента интерфейса WPF и объекты ViewModel созданы на одном уровне - UI-thread. Добавьте несколько журналов ('System.Threading.Thread.CurrentThread.ManagedThreadId'), чтобы узнать, в каком потоке вы находитесь и где. – Noseratio

ответ

7

Я считаю наиболее полезным рассматривать ViewModel как имеющую сходство нитей UI. Подумайте об этом как о логическом интерфейсе, даже если это не фактический пользовательский интерфейс . Таким образом, все свойства и наблюдаемые обновления коллекции на классах ViewModel должны выполняться в потоке пользовательского интерфейса.

В ваших методах async, если вам не нужно возвращаться к потоку пользовательского интерфейса, вы можете использовать ConfigureAwait(false), чтобы избежать возобновления потока пользовательского интерфейса. Например, если ваши различные методы инициализации независимы, вы могли бы сделать что-то вроде этого:

private async Task<BaseData> InitializeAsync() 
{ 
    // Start all methods on the UI thread. 
    var instancesTask = InstancesAsync(); 
    var projectsTask = ProjectsAsync(); 
    var adminTask = AdminAsync(); 

    // Await for them all to complete, and resume this method on a background thread. 
    await Task.WhenAll(instancesTask, projectsTask, adminTask).ConfigureAwait(false); 

    return this; 
} 

Кроме того, в любое время у вас есть return await, еще раз взглянуть, чтобы увидеть, если вы можете просто избежать async/await целиком:

public static Task<BaseData> CreateAsync() 
{ 
    var ret = new BaseData(); 
    return ret.InitializeAsync(); 
} 

Наконец, вы должны избегать Dispatcher. Ваше Loaded событие может быть упрощено:

Loaded += async() 
{ 
    DataContext = await BasisGegevens.CreateAsync(); 
}; 
+0

Я получил 'return await' из вашего шаблона фабрики в 'http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html'. – Gerard

+0

'Task.WhenAll': задачи должны выполняться в определенном порядке. – Gerard

+0

Загруженный делегат Я должен написать как 'async (отправитель объекта, RoutedEventArgs e)' для его компиляции. – Gerard

Смежные вопросы