Использование Task для обертывания синхронного кода

Сегодня мы рассмотрим довольно распространенный вопрос по поводу использования Task’а как обертки некого синхронного кода.

Представьте, что у вас есть интерфейс:

public interface IAsyncCommand
{
  Task ExecuteAsync();
}

Теперь представьте, что вы хотите реализовать этот интерфейс, но код сработает на самом деле не асинхронно. Это задача не ресурсоемкая и не требует своего собственного потока. Это просто обычный кусок синхронного кода. Есть три способа сделать это:

public class AsyncCommand1 : IAsyncCommand
 {
   public Task ExecuteAsync()
   {
       int x = 2 + 2;
       return Task.FromResult(true);
   }
 }

public class AsyncCommand2 : IAsyncCommand
{
  public async Task ExecuteAsync()
  {
    int x = 2 + 2;
  }
}

public class AsyncCommand3 : IAsyncCommand
{
  public Task ExecuteAsync()
  {
    return Task.Run(() => { int x = 2 + 2; })
  }
}

Так в чем же разница между этими тремя реализациями? С точки зрения генерации IL кода ответов довольно много. С точки зрения относительной производительности эти реализации попадают строго в категорию микро-оптимизации.

 

Код

Первая реализация является наиболее оптимальным подходом. Метод возвращает Task  со внутренним конструктором, который принимает результат. Статический метод FromResult() это просто публичная обертка конструктора, который возвращает выполненную задачу с вашим значением.

Второй подход я нахожу попросту более простым для чтения, потому что вам просто надо добавить async и все. НО он генерирует довольно много IL кода. Вы получаете полный созданный конечный автомат, который инициализируется, а затем выполняется.

[AsyncStateMachine(typeof (Class1.<ExecuteAsync>d__0))]
 [DebuggerStepThrough]
 public Task ExecuteAsync()
 {
   Class1.<ExecuteAsync>d__0 stateMachine;
   stateMachine.<>4__this = this;
   stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
   stateMachine.<>1__state = -1;
   stateMachine.<>t__builder.Start<Class1.<ExecuteAsync>d__0>(ref stateMachine);
   return stateMachine.<>t__builder.Task;
 } 

[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <ExecuteAsync>d__0 : IAsyncStateMachine
{
  public int <>1__state;
  public AsyncTaskMethodBuilder <>t__builder;
  public Class1 <>4__this;
  void IAsyncStateMachine.MoveNext()
  {
    try
    {
      if (this.<>1__state != -3)
      ;
    }
    catch (Exception ex)
    {
      this.<>1__state = -2;
      this.<>t__builder.SetException(ex);
      return;
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
  } 

  [DebuggerHidden]
  void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
  {
    this.<>t__builder.SetStateMachine(param0);
  }
}

Т.е. очень много сгенерированного кода только для того что бы защитить меня от явно возвращаемого Task’a!

Третий вариант генерирует код лямбда-выражения, а затем передает его в Task. Затем Task передается планировщику и то, что происходит дальше, зависит от вашего планировщика. Хотя этот вариант легче по кодогенерации, процесс “hand-off” делает его самым медленным.

 

Производительность

Я запустил несколько контрольных измерений по этими трем реализациям. В каждом случае метод срабатывал 100000 раз. Первая реализация отработала где-то за 2 мс, вторая  — за 15 мс и третья —  за 170 мс. Как я уже и говорил — все это всего лишь микро-оптимизация. Что интересно отметить, если вы повторите тест с присоединенным отладчиком — третий вариант срабатывает более чем за 30000 мс! Я предполагаю, что еще несколько переключений происходит с присоединенным отладчиком, которые влияют на производительность

 

Заключение

В идеале вы захотите не делать ничего из этого. Ваш лучший вариант заключается в поддержке как синхронных и асинхронных реализаций (в случае необходимости). На практике я часто сталкиваюсь, что часто нужно обрабатывать процесс обертывания (использования Task) и так удобней, но необходимо понимать, что именно Вы просите сделать компилятор в таких случаях. Это может быть микро-оптимизацией, но это не мешает Вам сделать это наилучшим образом!

 

Ссылка на источник: Wrapping synchronous code in a Task returning method

Реклама
Tagged with: , , , , , , ,
Опубликовано в .Net, Development

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s

%d такие блоггеры, как: