bkjvbx
bkjvbx

Reputation: 989

How to run possibly synchronous or possibly asynchronous code?

I've got a class with some async methods that take a handler class in order to decide what to do on success or failure.

public class FooModel
{
    private FooState fooState;

    public async Task UpdateFooAsync(IHandler handler)
    {
        try
        {
            var newFooState = await Something();
            fooState = newFooState;

            handler.Success();
        }
        catch
        {
            handler.Error();
        }
    }
}

public interface IHandler
{
    void Success();
    void Error();
}

I might call UpdateFooAsync from several different places in my code. And depending on where I call it from, I might want to do different things. For example, a synchronous example:

public async void update_click(object sender, RoutedEventArgs e)
{
    await UpdateFooAsync(new Handler1());
}

public class Handler1 : IHandler
{
    public void Success()
    {
        Provider.Page.text = "hello";
    }
    public void Error() {}
}

But I might also want to do an asynchronous operation after:

private async void updateAndThenSomething_click(object sender, RoutedEventArgs e)
{
    await UpdateFooAsync(new Handler2());
}

// this will generate not implemented error obviously
public class Handler2 : IHandler
{
    public async void SuccessAsync()
    {
        await Provider.OperationAsync();
    }
    public void Error() {}
}

But FooModel doesn't know whether I want to do a sync or async operation next. And it shouldn't have to know. Is there a way to have handler.Success() run either the synchronous code or the async code? I read some of Stephen Toub's stuff (link) which seems to imply I shouldn't make Success a Task and then await that task in the UpdateFooAsync try block.

I guess the other option is to dispense with the handler interface entirely. But that would require a lot of refactoring. The above situation happens a lot in my code, often with more than just success and error. I want to force someone using my code to specify what happens on success, error, or whatever. That's why I was using the interface. I guess I could also do that by throwing different kind of exceptions, but I'm not sure that's a good idea either.

Upvotes: 3

Views: 66

Answers (1)

NeddySpaghetti
NeddySpaghetti

Reputation: 13495

You can declare your handler interface to return a Task and then in the synchronous case just return using Task.FromResult. E.g.

public interface IHandler
{
    Task Success();
    void Error();
}

public class Handler1 : IHandler
{
    public Task Success()
    {
        Provider.Page.text = "hello";
        return Task.FromResult<object>(null); 
    }
    public void Error() {}
}

public class Handler2 : IHandler
{
    public async Task Success()
    {
        return Provider.OperationAsync();
    }

    public void Error() {}
}

Upvotes: 3

Related Questions