异步匿名无参数方法作为方法参数

时间:2015-01-30 15:17:25

标签: c# asynchronous mvvm lambda async-await

我有一个类用作我所有ViewModel的父级。它包含一个用于调用其他方法的特定方法,并显示错误加载消息和消息框(主要是):

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();
        }
    }
}

它是异步的,所以我的ShowLoading可以设置动画,就像那样。

  1. 是否正确实施?
  2. 它总是会得到匿名(lambda)无参数Task。我的主要问题是如何实际构建这些Task。我们假设我在Command的孩子中有一个ViewModelBase,在执行时会调用以下方法:

    private void OnMyCommandExecute()
    {
        Try(() =>
        {
            Thread.Sleep(5000);
        }, "error", "please wait");
    }
    

    因为Not all code paths return a value in lambda expression of type 'System.Func<System.Threading.Tasks.Task>'而无法编译。很明显,因为我们await这个Func。这引出了我的第二个问题:

    1. 在此示例中我应该将Try调用内容放在哪里才能使其正常工作?
    2. 我尝试了一些非常丑陋的东西,我真的希望答案有所不同,否则它将是一种可读性的痛苦:

      Try(async () =>
      {
          return await Task.Factory.StartNew(() =>
          {
              SharePointService.Connect(Connection);
              IsConnected = true;
          });
      }
      

      它没有编译,但在这一点上,它更好。 return上的错误:Since 'System.Func<System.Threading.Tasks.Task>' is an异步method that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task<T>'?

3 个答案:

答案 0 :(得分:3)

Try接受一个返回Task的方法。在您的第一个示例中,您提供的方法是void

在第二个示例中,您提供的方法返回Task<Task>,但尝试在期望Task(非泛型)的上下文中使用它。

如果你想使用非异步lambda,那么让lambda返回你想要使用的Task

Try(()=>Task.Factory.StartNew(() =>
    {
        SharePointService.Connect(Connection);
        IsConnected = true;
    }));

如果你想使用async lambda,那么你需要等待任务而不返回它:

Try(async () => await Task.Factory.StartNew(() =>
    {
        SharePointService.Connect(Connection);
        IsConnected = true;
    }));

请注意,在这里使用异步lambda没有任何实际意义。这两个片段的执行方式相同,但第二个片段在代码膨胀中增加了一些额外的开销,以及在运行时实际上并不需要的整个状态机。

答案 1 :(得分:1)

  

在此示例中,我应该在我的Try调用中放置什么才能使其正常工作?

你需要通过添加(令人惊讶的)async来创建lambda表达式async

Try(async () =>
{
    Thread.Sleep(5000);
}, "error", "please wait");

但是,虽然这将使您能够创建一个async委托,但实际上没有任何异步(它使用Thread.Sleep阻止调用线程)。如果这只是一个例子,那么:

Try(async () =>
{
    await Task.Delay(5000);
}, "error", "please wait");

是一个更好的。如果它根本不使用async

  

是否正确实施?

不是真的。几乎总是应该避免使用async void(除非在UI事件处理程序中)。请改用async Task并确保await返回的任务在某一点上确保操作已完成,没有任何例外。

答案 2 :(得分:0)

为了使Try尽可能透明,我最终得到了这个。

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();
    }
}

因此,当您致电时,您无需使用Task.Factory.StartNewasync/await

Try(() =>
{
    Thread.Sleep(5000);
}, "error", "please wait");