因此,我有以下代码,除了“设置”或“删除”以外,还有其他一些方法,但是为了简化起见,我将其简短化:
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();
是异步还是同步?
奖金问题:是否可以测试某些东西是否异步运行?我可以手动验证代码“异步性”的一种方式
答案 0 :(得分:6)
因此要介绍的第一件事是异步方法并不意味着“它上面带有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
{ {1}}表达式。如果您将代码更改为:
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
这是使事情变得复杂的地方。在我们上面的示例中,两个方法都返回 something ,因此调用了此重载。这些东西都是某种任务。因此,此代码将检查条件是否正常,如果与同步版本一样为假,则继续执行“ //执行其他操作”。不幸的是,它随后将继续返回默认值,对于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
,其结果是默认值。
如果条件为true(尽管它将调用异步方法),则在完成时计算代表该操作结果的任务,然后将其返回。与此相关的是,如果操作最终在某个时刻失败并返回错误的任务,则您的catch块将不会运行。 Task<T>
块仅在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);
}