2016-06-14 4 views
0

Я очень новичок в использовании async/await. Я пытаюсь аннулировать асинхронность и await условно в пользовательском интерфейсе. У меня есть абстрактный базовый класс:Ожидание от абстрактной асинхронной задачи

public abstract class Base 
{ 
    public abstract bool IsRunning { get; } 
    public abstract Task<bool> Run(); 
} 

и из него некоторых производных экземпляров, первый из которых синхронными:

internal class Derived1 : Base 
{ 
    private readonly Base baseCase; 
    private Task<bool> task; 
    public Derived1(Base baseCase) 
    { 
     this.baseCase = baseCase; 
    } 
    public override bool IsRunning 
    { 
     get { return false; } 
    } 
    public override Task<bool> Run() 
    { 
     task = new Task<bool>(() => 
     { 
      bool ok = DoSomething(); 
      return ok; 
     }); 
     return task; 
    } 
} 

и производный класс для асинхронной реализации:

internal class Derived2 : Base 
{ 
    private readonly Base baseCase; 
    private Task<bool> task; 
    public Derived2(Base baseCase) 
    { 
     this.baseCase = baseCase; 
    } 
    public override bool IsRunning 
    { 
     get { return task != null && task.Status == TaskStatus.Running; } 
    } 
    public override Task<bool> Run() 
    { 
     task = new Task<bool>(() => 
     { 
      bool ok = DoSomething(); 
      return ok; 
     }); 
     return task; 
    } 
} 

Тогда в пользовательском интерфейсе я хотел бы задать await по задаче asynchronous (если пользователь указан так в конфигурации времени выполнения):

internal class CaseMenuHandler 
{ 
    private async void OnRun(object sender, EventArgs args) 
    { 
     foreach (var case in Cases) 
     { 
      Base baseCaseRunner = GetCaseRunner(case); 
      try 
      { 
       bool ok = true; 
       if(something_holds) { 
        ok = await baseCaseRunner.Run(); 
       } 
       else { 
        ok = baseCaseRunner.Run().Result; 
       } 
      } 
      catch (Exception e) 
      { 
       LogError(...); 
      } 
     } 
    } 

Надеюсь, что это ясно. Могу ли я сделать это, особенно ожидая условно внутри блока if? В идеале я хотел бы сделать класс Base только возвратом bool, а не Task<bool> для метода Run, и только для переопределения класса Derived2 для возврата Task<bool>, но я не знаю, как это сделать. Возможно, я должен вернуть task.Result в метод RunDerived2? Если есть лучший способ для этого, включая абстракцию или любые другие исправления, пожалуйста, дайте мне знать. Цените любые идеи.

РЕДАКТИРОВАТЬ # 1

Run метод форма для синхронной реализации в Derived1 было выяснено в приведенных ниже ответов. Я не имею права изменить подпись метода DoSomething хотя, так что, учитывая, что мой Run метод Derived2 (асинхронной реализации) выглядит следующим образом теперь (спасибо @ комментарий Стриплинг компании):

public override async Task<bool> Run() 
    { 
     task = new Task<bool>(() => 
     { 
      bool ok = DoSomething(); 
      return ok; 
     }); 
     task.Start(); 
     return await task; 
    } 

EDIT # 2:

Когда я пытаюсь выше (также пытался ставить task.Start() вызов после task определения, я получаю следующее сообщение об ошибке:

Cross-thread operation not valid: Application accessed domain object from a thread other than a legal thread. 
+0

Можете ли вы объяснить, что вы пытаетесь сделать на условиях высокого уровня? –

+0

@YacoubMassad: Позвольте мне попробовать. Я пытаюсь иметь абстрактный класс и один производный класс, который реализует некоторые методы асинхронно, в то время как другой производный класс не делает этого, а затем в пользовательском интерфейсе, чтобы условно ждать, в зависимости от того, в каком случае я вхожу. –

+2

Взгляните на это: http://blog.stephencleary.com/2013/01/async-oop-1-inheritance-and-interfaces.html –

ответ

4

Can I do the above, specifically awaiting conditionally inside an if block?

Вы можете , но вы не должны. Если вы все сделаете правильно, нет особого преимущества в том, чтобы специально вызвать синхронную задачу в блокирующем режиме: как правило, вы можете просто await вернуть Задачу, а если она представляет собой синхронную задачу, то await будет разрешаться синхронно с очень мало накладных расходов.

Когда я говорю «если вы делаете все правильно», вот правильный путь:

// synchronous 
public override Task<bool> Run() 
{ 
    var result = DoSomething(); 
    return Task.FromResult(result); 
} 


// asynchronous 
public override async Task<bool> Run() 
{ 
    var result = await DoSomethingAsync(); 
    return result; 
} 

await ИНГ результат первого примера выше, не будет делать какие-либо нить переключение или что-нибудь подобное. await Результат второго могущества, в зависимости от реализации DoSomethingAsync(). Нет особой необходимости в слое абстракции: вы всегда можете проверить Task на то, завершено ли оно или нет, и await Выполнение уже завершенной задачи всегда будет немедленно возвращать значение.

+1

Спасибо, хорошо знать, что я мог бы использовать ожидание любой задачи. Тогда мне даже не нужно условие if. –

+0

Но что, если для асинхронного метода Run в вашем примере я не могу использовать wait на DoSomethingAsync, как вы это делали (т. Е. Я не хочу использовать ключевое слово async, скорее верну только Task ), так как моя задача определена с помощью лямбда функция. Думаю, теперь мой вопрос заключается в том, как использовать ключевое слово async/await, если мне нужно использовать функцию лямбда для создания экземпляра задачи? –

+0

Я нашел ответ на мой вопрос выше. См. Мое редактирование # 1. Я проверю это и вернусь в ближайшее время .. спасибо –

0

Самый простой способ сделать это, чтобы следовать стандартам: обеспечить синхронизацию и асинхронной путь:

public abstract class Base 
{ 
    public abstract bool IsRunning { get; } 
    public abstract bool Run(); 
    public abstract Task<bool> RunAsync(); 
} 

public sealed class Implementer : Base 
{ 
    //... /*DoSomething returns a bool*/ 
    public override bool Run() { return DoSomething().Result; } //SYNC 
    public override async Task<bool> RunAsync() { return await DoSomething(); } //ASYNC 
    //... 
} 
+0

Я вижу вашу точку зрения, и это был мой первоначальный подход, но меня «попросили» избежать этого подхода в пользу другого. –

2

Я не вижу, как ваша синхронная версия является синхронным, вы все еще используете Task.Run(, я бы ожидал

internal class Derived1 : Base 
{ 
    private readonly Base baseCase; 
    private Task<bool> task; 
    public Derived1(Base baseCase) 
    { 
     this.baseCase = baseCase; 
    } 
    public override bool IsRunning 
    { 
     get { return false; }  

    } 

    public override Task<bool> Run() 
    { 
     bool ok = DoSomething(); 
     return Task.FromResult(ok); 
    } 
} 

Если вы делаете это таким образом вместо того, чтобы, как вы делаете это, ваш другой код только становится

private async void OnRun(object sender, EventArgs args) 
{ 
    foreach (var case in Cases) 
    { 
     Base baseCaseRunner = GetCaseRunner(case); 
     try 
     { 
      bool ok = true; 
      ok = await baseCaseRunner.Run(); 
     } 
     catch (Exception e) 
     { 
      LogError(...); 
     } 
    } 
} 

асинхронный хроническая версия будет выполняться асинхронно, а синхронная версия будет работать синхронно.

Ваша «асинхронная версия» на самом деле не асинхронна, см. Stripling's answer для правильного способа выполнения этого метода.

0

Используйте Task.FromResult, чтобы вернуть задачу, которая содержит результат, который был вычислен синхронно и ждет.

bool ok = DoSomething(); 
return Task.FromResult(ok); 

Как примечание стороны я бы не поставил синхронный код в методе, который был в основном предназначен для асинхронных (или, что является синхронным в других классы/местах), и наоборот. Я бы использовал разные интерфейсы/базовые классы для синхронной и асинхронной реализации.

interface IRunnable 
{ 
    bool IsRunning; 
    bool Run(); 
} 

interface IRunnableAsync 
{ 
    bool IsRunning; 
    Task<bool> RunAsync(); 
} 
+0

'task.Result' не является обязательным, он может просто ждать его, как обычную задачу, и получить результат. –

+0

А, мне плохо, спасибо. – EvilTak

2

Then in the UI, I would like to await on asynchronous task (if user specified so in a run-time config)

Во-первых, я должен сказать, что это очень плохая идея. Некоторые вещи должны быть настраиваемыми, а некоторые не должны. Асинхронность операций не должна настраиваться. Период. Это ужасный дизайн, и первое, что я хотел бы сделать, это оттолкнуть назад от смешных «требований», подобных этому. Он буквально делает такой же смысл, как настраиваемый флаг для того, нужно ли генерировать исключения.

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

Если вы должны сделать это по политическим причинам (нет абсолютно никаких обоснованных технических причин), то я рекомендую вам использовать Boolean Argument Hack from my article on brownfield async. Одна проблема с просто блокировкой (Result) заключается в том, что она не работает, если Run использует await (для reasons described on my blog).

Логический аргумент hack просто добавляет логический аргумент, указывающий, будет ли метод выполняться синхронно или нет.

public abstract class Base 
{ 
    public abstract Task<bool> RunAsync(bool sync); 
} 

Семантика здесь, что если sync является true, то задача возвращается из RunAsyncдолжен уже завершен.

Ваша реализация тогда выглядит следующим образом:

internal class Derived1 : Base 
{ 
    public override async Task<bool> RunAsync(bool sync) 
    { 
    IsRunning = true; 
    try 
    { 
     if (sync) 
     return DoSomething(); 
     return await Task.Run(() => DoSomething()); 
    } 
    finally 
    { 
     IsRunning = false; 
    } 
    } 
} 

И это называется так:

private async void OnRun(object sender, EventArgs args) 
{ 
    foreach (var case in Cases) 
    { 
    Base baseCaseRunner = GetCaseRunner(case); 
    try 
    { 
     bool sync = !something_holds; 
     bool ok = await baseCaseRunner.RunAsync(sync); 
    } 
    catch (Exception e) 
    { 
     LogError(...); 
    } 
    } 
} 

Обратите внимание, что он всегда может быть называется с await, но OnRun на самом деле будет синхронную если sync - true. Это связано с «ожиданием быстрого пути» - тем фактом, что awaitfirst checks whether the task is already complete, and if it is, it continues synchronously (как описано в моем сообщении в блоге async intro).

+0

Неплохо ли иметь объект Task как частный член в классе? Во всех примерах, которые я видел, это похоже на то, что вы показали и выглядели так, как будто вы смогли захватить статус IsRunning. Но эквивалентен ли это тому, что частный объект Task проверяет статус IsRunning? Также можно использовать ваш подход, возможно, исправить ошибку, которую я получаю в Edit # 2? Ошибка при возврате из метода Run. –

+0

@ squashed.bugaboo: Это не * плохой * иметь 'Task' как членов, но иметь член 'Task', который представляет выполнение метода в этом же классе, является довольно нечетным. Мне это казалось ненужным. Ошибка поперечной резьбы, вероятно, все еще произойдет; если вы не можете использовать потоки пулов потоков (как подсказывает ошибка), тогда вам нужно изменить 'DoSomething' как асинхронный (и взять параметр' bool sync'). –

+0

@ squashed.bugaboo: Кажется, у вас есть два разных подхода, и правильное решение зависит от того, какой подход необходим. ** Если ** у вас есть интерфейс с методом, который может быть реализован синхронно или асинхронно, то «Task.FromResult» - отличное решение для синхронной реализации. ** Если ** у вас есть настраиваемый параметр, где * вызывающий код * должен обеспечивать, является ли * вызванный код * синхронным или асинхронным, тогда необходимо использовать логический аргумент. –

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

  • Нет связанных вопросов^_^