Изменить переопределенный элемент на асинхронный - программирование
Подтвердить что ты не робот

Изменить переопределенный элемент на асинхронный

Я переопределяю метод в библиотеке базового класса. Однако внутри моей переопределенной реализации я использую новый HttpClient, который основан на методах async. Поэтому я должен пометить свой метод как async, а это значит, что мне нужно изменить возвращаемый параметр метода из строки в Task. Однако компилятор сообщает об ошибке: "Тип возврата должен быть" string "для соответствия переопределенному элементу..."

    public class BaseClass
    {
        public virtual string GetName()
        {
            ...
        }
    }

    public class MyClass : BaseClass
    {
        public override async Task<string> GetName()
        {
            HttpClient httpClient = new HttpClient();
            var response = await httpClient.GetAsync("");
            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content;

                return await responseContent.ReadAsStringAsync();
            }

            return null;
        }
    }
Конечно, очевидным решением было бы изменить возвращаемый тип GetName() в BaseClass на Task <string> , но я не контролирую BaseClass, поскольку это внешняя библиотека;

Мое текущее решение - использовать классы HttpClient синхронно, т.е. изменить MyClass следующим образом:

    public class MyClass : BaseClass
    {
        public override string GetName()
        {
            HttpClient httpClient = new HttpClient();
            var response = httpClient.GetAsync("");
            if (response.Result.IsSuccessStatusCode)
            {
                var responseContent = response.Result.Content;

                return responseContent.ReadAsStringAsync()
                                                       .Result;
            }

            return null;
        }
    }

Есть ли другой способ сделать это?

4b9b3361

Ответ 1

К сожалению, здесь нет хорошего решения. Нет способа override неасинхронного метода с асинхронным. Я считаю, что лучше всего иметь метод async non-override и называть его неасинхронным:

public class MyClass : BaseClass 
{
    public override string GetName() 
    {
        return GetNameAsync().Value;
    }

    public async Task<string> GetNameAsync() 
    { 
        ...
    }
}

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

Ответ 2

К счастью, ReadAsStringAsync().Result не вызывает взаимоблокировки, поскольку он может иметь ConfigureAwait(false) внутри.

Чтобы предотвратить тупик, вы можете использовать один из следующих способов:

public static T GetResult<T>(Func<Task<T>> func)
{
    var httpContext = HttpContext.Context;

    var proxyTask = Task.Run(() =>
    {
        HttpContext.Context = httpContext;
        return func();
    });

    return proxyTask.Result;
}

// or

public static T GetResult<T>(Func<Task<T>> func)
{
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    var task = func();

    SynchronizationContext.SetSynchronizationContext(syncContext);

    return task.Result;
}

Таким образом вы бы назвали

public override string GetName()
{
    ...
    return GetResult(() => responseContent.ReadAsStringAsync());
    ...
}

У первого есть накладные расходы на производительность, создавая новый поток, в то время как последний страдает от разрыва потока SynchronizationContext, что делает любой контекст привязанным к нему недоступным в вызываемой задаче, например. HttpContext.Current.

Ответ 3

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

public abstract class Base : IInvokable {
    /* Other properties ... */

    public virtual async Task Invoke() {
        /*...*/
    }
}

public interface IInvokable {
    Task Invoke();
}

public class Derived 
{
    public override async Task Invoke() {
        // Your code here
    }
}