squashed.bugaboo
squashed.bugaboo

Reputation: 1356

Await on abstracted asynchronous task

I am very new to the async/await usage. I am trying to abstract the asynchrony and await conditionally in the UI. I have an abstract base class:

public abstract class Base
{
    public abstract bool IsRunning { get; }
    public abstract Task<bool> Run();
}

and from it some derived instances, first one being synchronous:

internal class Derived1 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived1(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return false; }
    }
    public override Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        return task;
    }
}

and a derived class for an asynchronous implementation:

internal class Derived2 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived2(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return task != null && task.Status == TaskStatus.Running; }
    }
    public override Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        return task;
    }
}

Then in the UI, I would like to await on asynchronous task (if user specified so in a run-time config), as follows:

internal class CaseMenuHandler
{
    private async void OnRun(object sender, EventArgs args)
    {
        foreach (var case in Cases)
        {
            Base baseCaseRunner = GetCaseRunner(case);
            try
            {
                bool ok = true;
                if( something_holds ) {
                    ok = await baseCaseRunner.Run();
                }
                else {
                    ok = baseCaseRunner.Run().Result;
                }
            }
            catch (Exception e)
            {
                LogError(...);
            }
        }
    }

Hope this is clear. Can I do the above, specifically awaiting conditionally inside an if block? Ideally I would like to make the Base class only return bool and not Task<bool> for the Run method, and only have the Derived2 class override to return a Task<bool>, but I am not clear on how to do that. Perhaps I should return the task.Result inside the Run method of Derived2? If there's a better way to this including the abstraction or any other corrections, please let me know. Appreciate any ideas.

EDIT #1

The Run method form for the synchronous implementation in Derived1 has been clarified in the responses below. I am not allowed to change the signature of the DoSomething method though, so given that, my Run method in Derived2 (asynchronous implementation) looks as follows now (thanks to @Stripling's comments):

    public override async Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        task.Start();
        return await task;
    }

EDIT #2:

When I try the above (also tried putting a task.Start() call after the task definition, I get the following error:

Cross-thread operation not valid: Application accessed domain object from a thread other than a legal thread.

Upvotes: 3

Views: 4590

Answers (4)

Stephen Cleary
Stephen Cleary

Reputation: 456457

Then in the UI, I would like to await on asynchronous task (if user specified so in a run-time config)

First off, I just have to say that this is a really bad idea. Some things should be configurable, and some should not. The asynchrony of operations should not be configurable. Period. This is a horrible design, and the first thing I would do is push back hard against ridiculous "requirements" like this. It literally makes the same amount of sense as a configurable flag for whether or not to throw exceptions.

That said, it can be done. It's painful, difficult to maintain, and completely useless at the end of the day. But hey, I assume you're getting paid for this so it's all good, eh?

If you must do this for political reasons (there are absolutely no valid technical reasons), then I recommend you use the Boolean Argument Hack from my article on brownfield async. One problem with just blocking (Result) is that it doesn't work if Run uses await (for reasons described on my blog).

The boolean argument hack simply adds a boolean argument indicating whether the method is expected to complete synchronously or not.

public abstract class Base
{
  public abstract Task<bool> RunAsync(bool sync);
}

The semantics here are that if sync is true, then the task returned from RunAsync must already be completed.

Your implementation then looks like:

internal class Derived1 : Base
{
  public override async Task<bool> RunAsync(bool sync)
  {
    IsRunning = true;
    try
    {
      if (sync)
        return DoSomething();
      return await Task.Run(() => DoSomething());
    }
    finally
    {
      IsRunning = false;
    }
  }
}

And it's called like:

private async void OnRun(object sender, EventArgs args)
{
  foreach (var case in Cases)
  {
    Base baseCaseRunner = GetCaseRunner(case);
    try
    {
      bool sync = !something_holds;
      bool ok = await baseCaseRunner.RunAsync(sync);
    }
    catch (Exception e)
    {
      LogError(...);
    }
  }
}

Note that it can always be called with await, but OnRun will actually be synchronous if sync is true. This is due to the "await fast path" - the fact that await first checks whether the task is already complete, and if it is, it continues synchronously (as described in my async intro blog post).

Upvotes: 2

StriplingWarrior
StriplingWarrior

Reputation: 156524

Can I do the above, specifically awaiting conditionally inside an if block?

You can, but you shouldn't have to. If you do things right, there's no great advantage to specifically invoking a synchronous task in a blocking fashion: as a general rule, you can just await the Task that's returned, and if it represents a synchronous task then the await will be resolved synchronously with very little overhead.

When I say "if you do things right", here's the right way:

// synchronous
public override Task<bool> Run()
{
    var result = DoSomething();
    return Task.FromResult(result);
}


// asynchronous
public override async Task<bool> Run()
{
    var result = await DoSomethingAsync();
    return result;
}

awaiting the result of the first example above will not do any thread-switching or anything like that. awaiting the result of the second might, depending on the implementation of DoSomethingAsync(). There's no particular need for a layer of abstraction: you can always check a Task to whether it's completed or not, and awaiting an already-completed task will always return a value immediately.

Upvotes: 8

Scott Chamberlain
Scott Chamberlain

Reputation: 127553

I don't see how your synchronous version is synchronous, you still use the Task.Run(, I would have expected

internal class Derived1 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived1(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return false; }    

    }

    public override Task<bool> Run()
    { 
        bool ok = DoSomething(); 
        return Task.FromResult(ok);
    }
}

If you do it that way instead of the way you are doing it, your other code just becomes

private async void OnRun(object sender, EventArgs args)
{
    foreach (var case in Cases)
    {
        Base baseCaseRunner = GetCaseRunner(case);
        try
        {
            bool ok = true;
            ok = await baseCaseRunner.Run();
        }
        catch (Exception e)
        {
            LogError(...);
        }
    }
}

The asynchronous version will run asynchronously and the synchronous version will run synchronously.

Your "async version" is not really async either, see Stripling's answer for the correct way to do that method.

Upvotes: 2

EvilTak
EvilTak

Reputation: 7579

Use Task.FromResult to return a task that contains the result that was computed synchronously, and await.

bool ok = DoSomething();
return Task.FromResult(ok);

As a side note I'd not put synchronous code in a method that was generally intended to be asynchronous (or that is synchronous in other classes/places) and vice versa. I'd use different interfaces/base classes for a synchronous and an asynchronous implementation.

interface IRunnable
{
    bool IsRunning;
    bool Run();
}

interface IRunnableAsync
{
    bool IsRunning;
    Task<bool> RunAsync();
}

Upvotes: 0

Related Questions