Jerrie Pelser
Jerrie Pelser

Reputation: 548

Change overridden member to async

I am overriding a method in a base class library. However, inside my overridden implementation I am using the new HttpClient which is all based on async methods. I therefore have to mark my method as async, which means that I need to change the return parameter of the method from string to Task. The compiler however gives an error: "The return type must be 'string' to match overridden member ...."

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

Of course the obvious solution would be to change the return type of GetName() in BaseClass to Task<string>, but I have no control over BaseClass as it is an external library;

My current solution is to use the HttpClient classes in a synchronous fashion, i.e. change MyClass as follows:

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

Is there any other way to do this?

Upvotes: 15

Views: 11677

Answers (3)

Herman Kan
Herman Kan

Reputation: 2292

Luckily the ReadAsStringAsync().Result is not causing a deadlock since it is likely to have ConfigureAwait(false) within.

To prevent a deadlock, you could use one of the following methods:

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

This way you would call

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

The former has a performance overhead by spawning a new thread, while the latter suffers from breaking SynchronizationContext flow, which makes any context bound to it unavailable in the task being called, e.g. HttpContext.Current.

Upvotes: 0

JaredPar
JaredPar

Reputation: 755269

Unfortunately there isn't a good solution here. There is no way to override a non-async method with an async one. I think your best bet is to have an async non-override method and call into that from the non-async one:

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

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

Note that this can cause problems though. If the original code didn't expect for any async code to be executing introducing this pattern could break expectations. I would avoid it if possible.

Upvotes: 10

smartpotato
smartpotato

Reputation: 1

I've also had this problem, and the solution was using an interface, in which 'async' isn't part of the method's signature.

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

Upvotes: -4

Related Questions