Изменить переопределенный элемент на асинхронный
Я переопределяю метод в библиотеке базового класса. Однако внутри моей переопределенной реализации я использую новый 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;
}
}
Есть ли другой способ сделать это?
Ответы
Ответ 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
}
}