2013-03-10 44 views
14

我重写了基类库中的方法。但是,在我重写的实现中,我使用的是全新的基于异步方法的HttpClient。因此,我必须将我的方法标记为async,这意味着我需要将该方法的返回参数从字符串更改为Task。然而,编译器提供了一个错误:“返回类型必须是‘串’可以匹配重写的成员......”将重写的成员更改为异步

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()的返回类型任务<字符串>,但我不能控制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; 
     } 
    } 

是否有任何其他方式做到这一点?

+0

不幸的是,没有好的解决方案(这违反了LSP)。你可以创建一个异步包装? – SLaks 2013-03-10 01:42:15

回答

10

不幸的是,这里没有一个好的解决方案。 override没有办法与异步的非异步方法。我认为最好的办法是有一个async非重写的方法,并从非异步调用一个成:

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

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

注意,这虽然可以导致问题。如果原始代码不希望任何async代码执行引入此模式可能会打破预期。如果可能的话,我会避免它。

+4

这个解决方案不是等待发生的僵局吗? – 2013-03-11 05:32:41

+1

@ G.Stoynev是的,这绝对有可能。正如我指出的那样,这是一个有问题的解决方案,我会尽可能地避免。 – JaredPar 2013-03-11 06:16:30

+0

谢谢@JaredPar。我知道这是有问题的,并会认真考虑:) – 2013-03-14 13:47:28

-5

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

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

OP states _“当然,显而易见的解决方案是将BaseClass中的GetName()的返回类型更改为任务,但**由于它是外部库,因此我无法控制BaseClass **”_ – MickyD 2016-01-21 01:48:27

0

幸运的是,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