带参数的异步 Lambda 表达式

5
我正在尝试从一个包装器中调用我的异步函数。但我得到了这个编译器错误。在下面的代码中,我想要将大部分等待操作抽象/推到CallAsyncFunc代码中,并获取string x值。在c# 4.5中,正确的方法是什么?谢谢!

错误 CS4010:无法将异步 lambda 表达式转换为委托类型 'System.Func<int,string>'。异步 lambda 表达式可以返回 voidTaskTask<T>,其中没有一个可转换为 'System.Func<int,string>'。

public async Task<T2> CallAsyncFunc<T1,T2>(T1 a, Func<T1, T2> func)
{
    return func.Invoke(a);
}

public async Task<string> GetString(int value)
{
    await Task.Run(() => Thread.Sleep(2000));
    return "" + value;
}

public async Task MainAsyncEntry()
{
    string x = await CallAsyncFunc<int, string>(30, async(x) => await GetString(x));
}

7
C# 不是 JavaScript。"" + value 应该改为 value.ToString() - Paulo Morgado
2个回答

8

输入:

string x = await CallAsyncFunc<int, string>(30, async(x) => await GetString(x));

你有两个变量 x,第一个出现问题。尝试:
string y = await CallAsyncFunc<int, string>(30, async(x) => await GetString(x));

这里lambda表达式的输出是string,但由于使用了async,它应该是一个TaskTask<T>

你可以这样写:

string y = await CallAsyncFunc<int, string>(30, x => GetString(x).Result);
// Equivalent to
string y = await CallAsyncFunc(30, x => GetString(x).Result);

或者

string y = await CallAsyncFunc<int, Task<string>>(30, x => GetString(x)).Result;
// Equivalent to
string y = await CallAsyncFunc(30, GetString).Result;

但无论哪种情况,如果CallAsyncFunc没有包含任何await,它都会同步运行。

这里是更好的做法:

// This
public async Task<TOut> CallAsyncFunc<TIn, TOut>(TIn input, Func<TIn, Task<TOut>> func)
{
    return await func.Invoke(input);
}

// Or those
public Task<TOut> CallAsyncFunc<TIn, TOut>(TIn input, Func<TIn, Task<TOut>> func)
{
    return func.Invoke(input);
}

public async Task<string> GetString(int value)
{
    await Task.Run(() => Thread.Sleep(2000));
    return value.ToString(CultureInfo.InvariantCulture);
}

public async Task MainAsyncEntry()
{
    string y = await CallAsyncFunc(30, GetString);
}

3
注意事项1: await GetString(x)Task<string>解封装为string,然后CallAsyncFunc再次将其封装为Task<string>。在您的调用堆栈中,请保留包装表达式的方式。据我所知,这是推荐的方法。
注意事项2: Resharper警告有关CallAsyncFunc: “这个异步方法缺少‘await’运算符,并且将同步运行。请考虑使用‘await’运算符等待非阻塞API调用,或使用‘await TaskEx.Run(…)’在后台线程上执行CPU绑定的工作。”
实际上,您可以省略async关键字并避免重新封装为Task<string>
基于注意事项1和注意事项2,您可以编写以下代码:
// REALLY ASYNC METHOD, WRAPS THE RESULT INTO Task<string>
public async Task<string> GetString(int value)
{
    await Task.Run(() => Thread.Sleep(2000));
    return "" + value;
}

// NOT ASYNC ANY MORE: DOES NOT WRAP THE RESULT INTO Task<T2> any more
public T2 CallAsyncFunc<T1, T2>(T1 a, Func<T1, T2> func)
{
    return func.Invoke(a);
}

// WAIT FOR THE ASYNC RESULT ONLY IN THE OUTER SCOPE (UNWRAPS Task<string> BY THE WAY)
string y = await CallAsyncFunc(30, GetString);
string z = await CallAsyncFunc<int, Task<string>>(30, GetString); 

太好了!这个可行。有没有办法减少一个await以获取最终值?目前,它是“string yval = await await CallAsyncFunc(..)”。 - tvr

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