【问题标题】:Change overridden member to async将覆盖的成员更改为异步
【发布时间】:2016-12-26 22:00:12
【问题描述】:

我正在重写基类库中的方法。但是,在我重写的实现中,我使用了新的 HttpClient,它全部基于异步方法。因此,我必须将我的方法标记为异步,这意味着我需要将方法的返回参数从字符串更改为任务。然而,编译器给出了一个错误:“返回类型必须是'字符串'才能匹配被覆盖的成员......”

    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;
        }
    }

当然,显而易见的解决方案是将 BaseClass 中 GetName() 的返回类型更改为 Task,但我无法控制 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;
        }
    }

还有其他方法吗?

【问题讨论】:

  • 很遗憾,没有好的解决方案(这违反了 LSP)。你能创建一个异步包装器吗?

标签: c# async-await


【解决方案1】:

很遗憾,这里没有好的解决方案。没有办法 override 使用异步方法的非异步方法。我认为你最好的选择是拥有一个 async 非覆盖方法并从非异步方法中调用它:

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

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

请注意,这可能会导致问题。如果原始代码不期望任何async 代码正在执行,则引入此模式可能会打破预期。如果可能,我会避免它。

【讨论】:

  • 这个解决方案难道不是一个等待发生的死锁吗?
  • @G.Stoynev 是的,这绝对是可能的。正如我所指出的,这是一个有问题的解决方案,我会尽可能避免。
  • 谢谢@JaredPar。我知道这是有问题的,并且会小心处理:)
  • @G.Stoynev 您需要将ConfigureAwait(false) 添加到任何await 调用中,是的。
  • “没有办法用异步方法覆盖非异步方法。” - 总是如此吗?我认为(我的实验似乎证实了这一点)当被覆盖的方法返回等待类型时是可能的。所以,我想更准确的说法是“现在有办法用异步方法覆盖非异步方法,除非它返回 void/Task/Task。”在这种特定情况下无关紧要,但我最终在这里想知道是否可以将非异步任务方法覆盖为异步任务方法,这似乎是正确的。 ;)
【解决方案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】:

    我也遇到过这个问题,解决方案是使用接口,其中“异步”不是方法签名的一部分。

    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
        }
    }
    

    【讨论】:

    • OP states "当然,显而易见的解决方案是将 BaseClass 中 GetName() 的返回类型更改为 Task,但 我无法控制 BaseClass是一个外部库"
    猜你喜欢
    • 2014-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    相关资源
    最近更新 更多