Kilazur
Kilazur

Reputation: 3188

Asynchronous anonymous parameterless method as a method parameter

I have a class used as a parent to all my ViewModels. 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.

  1. Is it correctly implemented?

It will always get anonymous (lambda) parameterless Tasks. My main issue is on how to actually construct these Tasks. 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:

  1. What should I put inside my 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 anasyncmethod 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

Answers (3)

Kilazur
Kilazur

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

i3arnon
i3arnon

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

Servy
Servy

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

Related Questions