Reputation: 1356
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
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
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;
}
await
ing the result of the first example above will not do any thread-switching or anything like that. await
ing 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 await
ing an already-completed task will always return a value immediately.
Upvotes: 8
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
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