因此,我有以下代码,除了Set或Delete以外,还有其他一些方法,但为简单起见,我将其简短化:
public byte[] Get(string key)
{
byte[] Action() => this.Cache.Get(key);
return this.Execute(Action);
}
public void Delete(string key)
{
void Action() => this.Cache.Delete(key);
return this.Execute(Action);
}
private void Execute(Action action)
{
this.Execute(() =>
{
action();
return 0;
});
}
private T Execute<T>(Func<T> action)
{
if (someCondition)
{
try
{
return action();
}
catch (Exception)
{
//do something
}
}
else
{
//do something else
}
return default(T);
}
现在,我想使此代码异步。我试着做:
public async Task<byte[]> GetAsync(string key)
{
async Task<byte[]> Action() => await this.Cache.GetAsync(key);
return await this.Execute(Action);
}
public async Task DeleteAsync(string key)
{
void Action() => this.Cache.DeleteAsync(key);
await this.Execute(Action);
}
private void Execute(Action action)
{
this.Execute(() =>
{
action();
return 0;
});
}
private T Execute<T>(Func<T> action)
{
if (someCondition)
{
try
{
return action();
}
catch (RedisConnectionException)
{
//do something
}
}
else
{
// do something else
}
return default(T);
}
它可以编译并且似乎可以工作,但是我不知道它是否实际上是异步的。似乎很奇怪,Execute方法不是异步的,并且不会返回Task。我没有找到一种方法来使Execute方法异步而不出现错误和警告。
所以我的问题是:在第二版代码中,将执行该动作
return action();
是异步还是同步?
额外的问题:是否可以测试某些东西是否异步运行?我可以手动验证代码“异步性”的一种方式
最佳答案
因此,首先要介绍的是,异步方法并不意味着“它上面带有async
关键字”。当一个方法非常快速地返回到调用方时,它是异步的,然后在返回给调用方并允许调用方继续执行它想要做的所有事情之后,完成对它的要求的所有操作。通常,这还涉及到某种方式,以便调用方知道操作何时完成,是否成功,有时还包括操作的某些结果。async
关键字所做的只是允许该方法在其中包含await
关键字。如果您未将方法标记为async
,则不会知道其中是否使用了await
实际上只是常规变量名而不是特殊关键字。 await
的意思是说,您希望该方法安排在等待任务完成后运行await
之后的代码。
考虑到这一点,我们可以遍历您的方法,看看它们在做什么,以及它们是否异步。
public async Task<byte[]> GetAsync(string key)
{
async Task<byte[]> Action() => await this.Cache.GetAsync(key);
return await this.Execute(Action);
}
因此,让我们先来看一下内部方法:
async Task<byte[]> Action() => await this.Cache.GetAsync(key);
这将执行异步操作
GetAsync
,安排连续操作在完成后运行,什么也没做...然后返回GetAsync
返回的确切结果。因此,除了添加续集涉及一些开销之外,此方法与只写相同:Task<byte[]> Action() => this.Cache.GetAsync(key);
现在,当我们查看该方法时:
public async Task<byte[]> GetAsync(string key)
{
return await this.Execute(Action);
}
我们可以看到,该方法也调用了异步方法,向其添加了一个继续执行的操作,没有执行任何操作,然后完全按原样返回执行该方法的结果。
现在,再次转到下一个方法,首先查看内部方法:
void Action() => this.Cache.DeleteAsync(key);
在这里,我们正在调用异步方法,但没有返回它给我们的
Task
。这意味着我们无法知道操作何时完成或是否成功。由于DeleteAsync
是异步的(或者可以假设给定名称),因此我们知道此方法将在启动异步操作后立即返回,而不是在基础操作完成之后返回。public async Task DeleteAsync(string key)
{
await this.Execute(Action);
}
这不会编译。
Action
这是一个返回void
的方法,因此您正在调用Execute
的重载,该重载返回void
,而您不能await
一个void
表达式。如果您将代码更改为:public async Task DeleteAsync(string key)
{
Task Action() => this.Cache.DeleteAsync(key);
await this.Execute(Action);
}
然后它将编译,因为您将调用接受
Execute
并返回结果的Func<T>
版本,因此您可以等待该任务,但是,正如我们从较早方法中看到的那样,等待它除了增加一些开销外没有任何其他用途,我们可以返回任务并完成任务。private void Execute(Action action)
{
this.Execute(() =>
{
action();
return 0;
});
}
如果我们进行上面指出的更改,就永远不会调用它,因为我们永远不会传递返回
void
的委托。private T Execute<T>(Func<T> action)
{
if (someCondition)
{
try
{
return action();
}
catch (RedisConnectionException)
{
//do something
}
}
else
{
// do something else
}
return default(T);
}
这就是事情变得复杂的地方。在我们上面的示例中,两个方法都返回一些内容,因此将调用此重载。这些东西都是某种任务。因此,此代码将检查条件是否正常,如果与同步版本一样为假,则继续执行“ //执行其他操作”。不幸的是,它随后将继续返回默认值,对于
Task
,默认值是null
。那可能很糟糕。当该任务返回时,某人可能会在某个时候执行await
任务,然后他们将仅获得空引用异常。调用者可能想要在此处发生的事情是获得一个Task<T>
,其结果为默认值。如果条件为真,尽管它将调用异步方法,但只要条件完成,就计算代表该操作结果的任务,然后将其返回。与此相关的是,如果操作最终在某个点失败并返回错误的任务,则您的catch块将不会运行。
catch
块仅在action
引发异常而不返回错误任务时运行。 (大多数异步方法不这样做。特别是,任何async
方法都不会这样做。)但是,重写
Execute
以拥有ExecuteAsync
版本非常简单。您需要接受函数,而不是接受返回某些结果或无效的函数返回
Task<T>
或Task
并返回Task<T>
或Task
的代码。除此之外,唯一真正要做的是在您希望其余代码在任务完成之前不运行的任何时间执行await
:private Task<T> ExecuteAsync<T>(Func<Task<T>> action)
{
if (someCondition)
{
try
{
return await action();
}
catch (RedisConnectionException)
{
//do something
}
}
else
{
// do something else
}
return default(T);
}
然后方法的重载没有完成任务:
private Task ExecuteAsync(Func<Task> action)
{
return this.ExecuteAsync(async () =>
{
await action();
return 0;
});
}
关于c# - 异步lambda是否应在异步功能中运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53123394/