使用通用的Func<T>异步方法

3

我写了下面这个方法:

async Task<T> Load<T>(Func<T> function)
{
    T result = await Task.Factory.StartNew(() =>
    {
        IsLoading = true;

        T functionResult = function.Invoke();

        IsLoading = false;

        return functionResult;
    });

    return result;
}

我有两个问题:

  1. Can I simplify the code?

  2. I can pass any parameterless method/function to this method to get a return type of any type. E.g.:

     string GetString()
    

    By saying:

     string someString = await Load(GetString);
    

    Is there a way I could make this method more generic so that I could pass methods with parameters as well? E.g. One single method that would also accept:

     string GetString(string someString)
     string GetString(string someString, int someInt)
    

    This might look like:

     string someString = await Load(GetString("string"));
     string someString = await Load(GetString("string", 1));
    

    This obviously doesn't work, but as the Load<T> method doesn't reference the parameters, I feel this should be possible somehow.


2
IsLoading 的目的是什么? - Robert Harvey
1
通常情况下,您不需要使用.Invoke()来调用委托。您可以简单地使用T functionResult = function()生成的代码没有区别,但我倾向于将.Invoke()与基于反射的动态调用相关联。 - xanatos
你确定你甚至需要第二个问题吗?await Load(() => GetString("string"))应该可以正常工作。 - Heinzi
@RobertHarvey IsLoading 是我在 WPF 数据绑定中使用的一个属性 - 这个方法是 LoadingViewModel 基类的一部分。 - Chris Mack
4个回答

3

我能简化代码吗?

你可以简化它,也可以使其更加正确。以下是代码存在的一些问题:

  1. IsLoading 是一个 UI 绑定属性,因此应该在 UI 线程上更新,而不是在后台线程上。一些像 Windows 上的 WPF 这样的框架允许你打破规则,但其他基于 XAML 的框架则不行。
  2. 如果加载失败,当前代码将永远不会将 IsLoading 设置为 false
  3. Task.Factory.StartNew 应该避免使用;它是一个危险的、低级别的方法。如果需要在后台线程上运行方法,请使用 Task.Run
async Task<T> Load<T>(Func<T> function)
{
  IsLoading = true;
  try
  {
    return await Task.Run(function);
  }
  finally
  {
    IsLoading = false;
  }
}

有没有办法让这个方法更加通用,以便我也可以传递带参数的方法?

您可以使用lambda表达式来实现:

string someString = await Load(() => GetString("string"));
string someString = await Load(() => GetString("string", 1));

谢谢你的回答 - 真是太好了! - Chris Mack

2
有没有办法让这个方法更通用,这样我也可以传递带参数的方法?
async Task<R> Load<T, R>(Func<T, R> function, T parameter)
{
    R result = await Task.Run(() =>
    {
        return function.Invoke(parameter);
    });

    return result;
}

你可以将函数所需的所有参数捆绑在 T 中。

如果你仍然需要单独的参数,则必须创建额外的重载。

你也可以尝试使用 params 传递任意数量的参数,但除非你在某种形式上使用运行时多态性,否则它们必须全部是相同类型的。


1
你可以稍微缩短它到:
Task<T> Load<T>(Func<T> function)
{
    return Task.Factory.StartNew(() =>
    {
        IsLoading = true;
        var functionResult = function.Invoke();
        IsLoading = false;

        return functionResult;
    });
}

由于可以返回任务,因此async和await是可选的。可能有意义使用try ... finally块并在finally中更新IsLoading = false。

  1. 我同意Robert Harvey关于额外参数的答案

0
有没有办法让这个方法更通用? 一种选择是像这样嵌套它:
    public bool IsLoading { get; set; }
    
    public Task<string> GetString(string input) => Task.FromResult($"ret for input {input}");

    public async Task<TReturn> Load<TReturn>(Func<Task<TReturn>> cb)
    {
        IsLoading = true;
        var ret = await cb();
        IsLoading = false;

        return ret;
    }

    public async Task Demo()
    {
        var ret = await Load(() => GetString("input"));
    }

我已将GetString()方法也改为返回一个任务。假设这在你的情况下很容易调整。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接