Как создать и реализовать интерфейсы для операций, которые только иногда являются асинхронными - PullRequest
2 голосов
/ 01 апреля 2019

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

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

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

Код, который у меня сейчас есть, это только один интерфейсный метод, который является асинхронным.Затем для синхронных реализаций они обертывают код внутри объекта Task:

using System.IO;
using System.Threading.Tasks;

namespace TestApp
{
    interface IBlackBox
    {
        Task<string> PullText();
    }

    sealed class MyAsyncBlackBox : IBlackBox
    {
        public async Task<string> PullText()
        {
            using (var reader = File.OpenText("Words.txt"))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }

    sealed class MyCachedBlackBox : IBlackBox
    {
        public Task<string> PullText()
        {
            return Task.Run(() => "hello world");
        }
    }
}

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

Ответы [ 3 ]

3 голосов
/ 01 апреля 2019

Это обычная ситуация с интерфейсами ... Если у вас есть контракт, в котором необходимо указать task для Асинхронного шаблона ожидания , и мы должны реализовать это Task в интерфейсе .

Предполагая, что вызывающий абонент будет использовать await, вы можете просто сбросить async и вернуть Task.

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

Обычное использование

Стандарт async

public async Task<string> PullText()
{
   using (var reader = File.OpenText("Words.txt"))
   {
      return await reader.ReadToEndAsync();
   }
}

Возвращение Task для Процессор с привязкой работа (захват)исключение и размещение его на Task)

public Task<string> PullText()
{
   try
   {
      return Task.Run(() => DoCpuWork());
   }
   catch (Exception e)
   {
      return Task.FromException<string>(e);
   }
}

Немного менее эффективно, так как мы подключаем IAsyncStateMachine

public async Task<string> PullText()
{
    return await Task.Run(() => DoCpuWork());
}

Возвращение завершено Task с простыми результатами (захват исключения и помещение его в Task)

public Task<string> PullText()
{
   try
   {
      // simplified example
      return Task.FromResult("someString");
   }
   catch (Exception e)
   {
      return Task.FromException<string>(e);
   }
}

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

#pragma warning disable 1998
public async Task<string> PullText()()
#pragma warning restore 1998
{
    return Task.Run(() => "hello world");
}

и

#pragma warning disable 1998
public async Task<string> PullText()()
#pragma warning restore 1998
{
    return Task.FromResult("someString");
}

Примечание Все вышеперечисленное касается возврата Task<T> из метода.Если кто-то просто хотел вернуть Task, вы можете воспользоваться Task.CompletedTask; с той же семантикой ошибок, что и выше.

1 голос
/ 01 апреля 2019

Обычно в этих случаях перед вызовом есть что-то, что обрабатывает запрос и передает его рабочим классам (например, TestApp).Если это так, я не понимаю, почему не работает интерфейс «IAsyncable», где вы можете проверить, поддерживает ли класс асинхронность.

if(thisObject is IAscyncAble) {
  ... call the ansync request.
}
0 голосов
/ 02 апреля 2019

Я использовал следующий код:

using System.IO;
using System.Threading.Tasks;

namespace TestApp
{
    interface IBlackBox // interface for both sync and async execution
    {
        Task<string> PullText();
    }

    sealed class MyAsyncBlackBox : IBlackBox
    {
        public async Task<string> PullText()
        {
            using (var reader = File.OpenText("Words.txt"))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }

    sealed class MyCachedBlackBox : IBlackBox
    {
        public Task<string> PullText() // notice no 'async' keyword
        {
            return Task.FromResult("hello world");
        }
    }
}
...