2015-08-12 3 views
5

У меня есть метод в моей модели представленияРазница между вызовом метода асинхронной и Task.Run асинхронный метод

private async void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     this.SyncContacts(); 
    } 
} 

private async Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // do synchronous data analysis 
    } 

    // ... 

    // AddContacts is an async method 
    CloudInstance.AddContacts(contactsToUpload); 
} 

Когда я называю SyncData из команд пользовательского интерфейса и я синхронизируется большой кусок интерфейса данных замерзает. Но когда я называю SyncContacts с этим подходом

private void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     Task.Run(() => this.SyncContacts()); 
    } 
} 

Все в порядке. Разве они не должны быть одинаковыми? Я думал, что не использовать ожидание вызова метода async создает новый поток.

+2

'async' не всегда означает, что задействованы другие потоки - см. [нет нити] (http://blog.stephencleary.com/2013/11/there-is-no-thread.html). –

+0

В вашей SyncData отсутствует ключевое слово ожидания, которое сообщит компилятору о продолжении другой обработки. На данный момент ваш метод SyncData должен блокировать, что правильно –

ответ

8

Разве они не должны быть одинаковыми? Я думал, что не использовать ожидание для , вызывающего метод async, создает новый поток.

No, async не магически выделяет новый поток для его вызова метода. async-await в основном использует естественные асинхронные API, такие как сетевой вызов в базу данных или удаленный веб-сервис.

Когда вы используете Task.Run, вы явно используете поток ниток потока для выполнения вашего делегата. Если вы помечаете метод с ключевым словом async, но ничего не делаете await, он будет выполняться синхронно.

Я не уверен, что ваш метод SyncContacts() на самом деле (так как вы не предоставили его реализацию), но его маркировка async сама по себе ничего не принесет.

Edit:

Теперь, когда вы добавили реализацию, я вижу две вещей:

  1. Я не знает, как ресурсоемкий ваш анализ синхронного данных, но это может быть достаточно, чтобы пользовательский интерфейс не реагировал.
  2. Вы не ожидаете своей асинхронной работы. Он должен выглядеть следующим образом:

    private async Task SyncDataAsync(SyncMessage syncMessage) 
    { 
        if (syncMessage.State == SyncState.SyncContacts) 
        { 
         await this.SyncContactsAsync(); 
        } 
    } 
    
    private Task SyncContactsAsync() 
    { 
        foreach(var contact in this.AllContacts) 
        { 
         // do synchronous data analysis 
        } 
    
        // ... 
    
        // AddContacts is an async method 
        return CloudInstance.AddContactsAsync(contactsToUpload); 
    } 
    
+0

Вы уверены, что явно использует пул потоков? AFAIK, он явно использует по умолчанию 'TaskScheduler' и этот _happens_ для объединения пула потоков. Мы могли бы это знать, но абстракции направлены на то, чтобы скрыть эту деталь и не дает никаких гарантий, что это может не измениться в следующей версии .net. – Gusdor

+4

@Gusdor ['Task.Run'] (http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,89fc01f3bb88eed9) по умолчанию [' TaskScheduler.Default'] (http: // referenceource.microsoft.com/#mscorlib/system/threading/Tasks/TaskScheduler.cs,d4a38cb3e3085518), который является планировщиком задач threadpools. –

+0

@ Гусдор, Юваль прав. Из MSDN: «Очереди указанной работы для выполнения на ThreadPool и возвращает задачу или Task для этой работы». –

0

Какую линию Task.Run(() => this.SyncContacts()); действительно создает новую задачу запустить его и вернуть его к абоненту (который не используется для каких-либо дальнейших целей в вашем случае). Именно по этой причине он будет работать в фоновом режиме, и пользовательский интерфейс продолжит работу. Если вам нужно (а) дождаться завершения задачи, вы можете использовать await Task.Run(() => this.SyncContacts());. Если вы просто хотите, чтобы SyncContacts завершил работу при возврате вашего метода SyncData, вы могли бы использовать возвращаемую задачу и ожидали ее в конце вашего метода SyncData. Как было предложено в комментариях: если вас не интересует, закончилась ли задача или нет, вы просто можете ее вернуть.

Однако Microsoft рекомендует не смешивать код блокировки и асинхронный код, а методы async заканчиваются на Async (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx). Поэтому вам следует рассмотреть возможность переименования ваших методов и не помечать методы с помощью async, если вы не используете ключевое слово ожидания.

+0

Это неверно. 'await' не имеет никакого отношения к тому, что-то работает в фоновом режиме или нет. Это связано с тем, как вы справляетесь с ожиданием завершения задачи и как вы обрабатываете ее ответ. MS не рекомендует использовать 'await' во всех случаях либо потому, что он вводит служебные данные. Если метод, возвращающий задачу, не должен ждать завершения задачи, нет причин использовать 'async/await', просто верните задачу как есть –

+1

Я не сказал, что ожидание запускает задачи в фоновом режиме. Я сказал, что Task.Run() сделает это. Во-вторых, вы правы, я должен сказать, что MS рекомендует не смешивать код блокировки и асинхронный код https://msdn.microsoft.com/en-us/magazine/jj991977.aspx. В любом случае, возвращение задачи вместо void обязательно должно быть сделано. – Daniel

0

Чтобы уточнить, почему пользовательский интерфейс замерзает - работа, выполняемая в узкой петле foreach, вероятно, связана с процессором и блокирует поток исходного звонка до тех пор, пока цикл не завершится.

Таким образом, независимо от того, возвратил Task из SyncContacts является await ред или нет, ЦП работы перед вызовом AddContactsAsync все равно будет происходить синхронно на, и блок, нить вызывающего абонента.

private Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // ** CPU intensive work here. 
    } 

    // Will return immediately with a Task which will complete asynchronously 
    return CloudInstance.AddContactsAsync(contactsToUpload); 
} 

(Re: Нет, почему async/return await на SyncContacts - смотри пункт Юваль в - создание метода асинхр и ожидает результат был бы wasteful in this instance)

Для проекта WPF, оно должно быть в порядке, чтобы использовать Task.Run, чтобы связать работу процессора с вызывающим потоком (но not so for MVC or WebAPI проектов Asp.Net).

Кроме того, предполагая, что contactsToUpload отображения работу потокобезопасно, и что ваше приложение имеет полное использование ресурсов пользователя, вы могли бы также рассмотреть распараллеливание отображения сократить общее время выполнения:

var contactsToUpload = this.AllContacts 
    .AsParallel() 
    .Select(contact => MapToUploadContact(contact)); 
    // or simpler, .Select(MapToUploadContact); 
Смежные вопросы