异步lamda是否应在异步功能中运行?

时间:2018-11-02 17:35:47

标签: c# asynchronous async-await

因此,我有以下代码,除了“设置”或“删除”以外,还有其他一些方法,但是为了简化起见,我将其简短化:

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

是异步还是同步?

奖金问题:是否可以测试某些东西是否异步运行?我可以手动验证代码“异步性”的一种方式

1 个答案:

答案 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版本非常简单。您需要接受函数,而不是接受返回某些结果或无效的函数 返回ExecuteAsyncTask<T>,并返回TaskTask<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);
}