async lamda应该在异步函数中运行吗?

Cho*_*man 1 c# asynchronous async-await

所以我有以下代码,除了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);
}
Run Code Online (Sandbox Code Playgroud)

现在我想让这段代码异步.我试过做:

 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);
}
Run Code Online (Sandbox Code Playgroud)

它编译并且似乎有效,但我不知道它是否实际上是异步的.Execute方法不是异步并且不返回任务似乎很奇怪.我没有找到一种方法来使Execute方法异步,而不会收到错误和警告.所以我的问题是:在代码的第二个版本中,将执行动作

return action();
Run Code Online (Sandbox Code Playgroud)

是异步还是同步?

奖金问题:有没有办法测试某些内容是否同步?我可以手动验证代码的"异步"的方法

Ser*_*rvy 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);
}
Run Code Online (Sandbox Code Playgroud)

所以我们先来看一下内部方法:

async Task<byte[]> Action() => await this.Cache.GetAsync(key);
Run Code Online (Sandbox Code Playgroud)

这将执行异步操作,GetAsync在完成后执行继续运行...没有...然后返回返回的确切结果GetAsync.因此,除了添加延续所涉及的一些开销之外,此方法与写出相同:

Task<byte[]> Action() => this.Cache.GetAsync(key);
Run Code Online (Sandbox Code Playgroud)

现在,当我们查看它的方法时:

public async Task<byte[]> GetAsync(string key)
{
    return await this.Execute(Action);
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到,这个方法也调用了一个异步方法,为它添加了一个继续,它没有...什么......然后返回完全按原样执行该方法的结果.

现在转到下一个方法,再次,首先看内部方法:

void Action() => this.Cache.DeleteAsync(key);
Run Code Online (Sandbox Code Playgroud)

这里我们调用一个异步方法,但是我们没有返回Task它给我们的方法.这意味着我们无法知道操作何时结束,或者是否成功.由于DeleteAsync是异步的(或者我们可以假设,给定名称),我们知道这个方法将在它启动异步操作后立即返回,而不是在基础操作完成之后.

public async Task DeleteAsync(string key)
{
    await this.Execute(Action);
}
Run Code Online (Sandbox Code Playgroud)

这不编译. Action这里是返回一个方法void,让你打电话的过载Execute返回void,和你不能await一个void表情.如果您将代码更改为:

public async Task DeleteAsync(string key)
{
    Task Action() => this.Cache.DeleteAsync(key);
    await this.Execute(Action);
}
Run Code Online (Sandbox Code Playgroud)

然后它会编译,因为你要调用Execute接受a 的版本Func<T>并返回结果,所以你可以等待那个任务,但是,正如我们从早期方法中看到的那样,等待它不会做一些有用的事情,除了增加一些开销,我们可以只返回任务并完成它.

private void Execute(Action action)
{
    this.Execute(() =>
    {
        action();
        return 0;
    });
}
Run Code Online (Sandbox Code Playgroud)

如果我们进行上面指出的更改,就不会调用它,因为我们永远不会传递返回的委托void.

private T Execute<T>(Func<T> action)
{
    if (someCondition)
    {
        try
        {
            return action();
        }
        catch (RedisConnectionException)
        {
            //do something
        }
    }
    else
    {
        // do something else
    }

    return default(T);
}
Run Code Online (Sandbox Code Playgroud)

这是事情变得复杂的地方.在上面的例子中,两个方法都返回一些东西,因此调用了这个重载.这些事情都是某种形式的任务.因此,此代码将检查条件就好了,如果它是假的,就像使用同步版本一样继续"//做其他事情".不幸的是,它会然后继续返回默认值,这为Tasknull.那可能很糟糕.当该任务返回时,某人可能await会在某个时刻,然后他们将获得空引用异常.调用者可能希望在这里发生的是得到一个Task<T>结果为默认值的结果.

如果条件为true,但它将调用异步方法,则计算表示该操作结果的任务,只要它完成,然后返回它.相关地,如果操作在某个时刻失败并且返回故障任务,则您的catch块将不会运行.catch只有在action抛出异常而不是返回故障任务时才会运行该块.(大多数异步方法都没有.特别是,任何async方法 都不会这样做.)

重写Execute以获得一个ExecuteAsync版本是相当简单的.您不需要接受返回某些结果或无效的函数,而是接受返回a Task<T>,或Task返回a 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);
}
Run Code Online (Sandbox Code Playgroud)

然后方法的重载没有导致他们的任务:

private Task ExecuteAsync(Func<Task> action)
{
    return this.ExecuteAsync(async () =>
    {
        await action();
        return 0;
    });
}
Run Code Online (Sandbox Code Playgroud)