Reputation: 3188
I have a class used as a parent to all my ViewModel
s. It contains a specific method used to call others methods, and show loading messages and message boxes on error (mainly):
public class BaseViewModel
{
async public void Try(Func<Task> action, string errorMessage = null, string waitMessage = null)
{
try
{
if (waitMessage != null)
ShowLoading(waitMessage);
await action();
}
catch (Exception e)
{
ShowError(errorMessage, e);
}
finally
{
HideLoading();
}
}
}
It is asynchronous, so my ShowLoading
can be animated and stuff like that.
It will always get anonymous (lambda) parameterless Task
s. My main issue is on how to actually construct these Task
s. Let's say I have a Command
in a ViewModelBase
's child, which call the following method when executed:
private void OnMyCommandExecute()
{
Try(() =>
{
Thread.Sleep(5000);
}, "error", "please wait");
}
It does not compile because Not all code paths return a value in lambda expression of type 'System.Func<System.Threading.Tasks.Task>'
. Obvious, since we await
this Func
. Which leads me to the second question:
Try
call in this example for it to work?I tried some really ugly things, and I really hope the answer is way different, else it will be a pain of readability:
Try(async () =>
{
return await Task.Factory.StartNew(() =>
{
SharePointService.Connect(Connection);
IsConnected = true;
});
}
It does not compile, but at this point, it's better like that. Error on return
: Since 'System.Func<System.Threading.Tasks.Task>' is an
asyncmethod that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task<T>'?
Upvotes: 0
Views: 931
Reputation: 3188
In order for Try
to be as transparent as possible, I ended up with this.
async public Task Try(Action action, string errorMessage = null, string waitMessage = null)
{
try
{
if (waitMessage != null)
{
ShowLoading(waitMessage);
await Task.Factory.StartNew(() => action());
}
else
action();
}
catch (Exception e)
{
ShowError(errorMessage, e);
}
finally
{
HideLoading();
}
}
Therefore, you don't have to work with Task.Factory.StartNew
or async/await
when you call it:
Try(() =>
{
Thread.Sleep(5000);
}, "error", "please wait");
Upvotes: 0
Reputation: 116548
What should I put inside my Try call in this example for it to work?
You need to make that lambda expression async
by adding (surprisingly) async
:
Try(async () =>
{
Thread.Sleep(5000);
}, "error", "please wait");
However, while this will enable you to create an async
delegate there's nothing actually asynchronous about it (it blocks the calling thread with Thread.Sleep
). If this is just an example then:
Try(async () =>
{
await Task.Delay(5000);
}, "error", "please wait");
is a better one. If it isn't don't use async
at all.
Is it correctly implemented?
Not really. async void
should almost always be avoided (unless in a UI event handler). Use async Task
instead and make sure to await
the returned task in some point to ensure the operation completed without any exceptions.
Upvotes: 1
Reputation: 203814
Try
accepts a method that returns a Task
. In your first example you're providing a method that is void
.
In your second example you're providing a method that returns a Task<Task>
, but trying to use it in a context where a Task
(non-generic) is expected.
If you want to use a non-async lambda, then just have that lambda return the Task
that you want to use:
Try(()=>Task.Factory.StartNew(() =>
{
SharePointService.Connect(Connection);
IsConnected = true;
}));
If you want to use an async
lambda, then you need to await the task without returning it:
Try(async () => await Task.Factory.StartNew(() =>
{
SharePointService.Connect(Connection);
IsConnected = true;
}));
Note that there's no real purpose to having an async lambda here. These two snippets will both perform identically, but the second adds some extra overhead in code bloat as well as a whole state machine that just isn't actually needed at runtime.
Upvotes: 3