异步/等待续集

7
在下面的代码中,我需要并行执行三个 Get... 方法。当一个 Get... 方法完成后,我需要立即调用 Save... 方法。请注意,Save... 以 thing 作为参数。 所有 Get 和 Save 方法必须在 DoStuffAsync 返回之前完成。
我的猜测是,我需要在 Get... 方法上进行继续操作,但我不知道如何构造它。
protected async void DoStuffAsync()
{
    SomeThing thing = new SomeThing { ID = 5 };
    SomeRepository rep = new SomeRepository();

    // We need to run all three Get... methods in parallel
    // As soon as a Get... method completes we need to save the result to the correct property on thing and call the Save... method .

    var getRed = rep.GetRedAsync().ContinueWith<Task<string>>(async x => { thing.Color1 = x.Result; await rep.SaveRedAsync(thing); return x; }); // does not compile
    var getBlue = rep.GetBlueAsync();
    var getGreen = rep.GetGreenAsync();

    string red = await getRed.Result; // this is not good because getBlue may finish before getRed.  We want dont want to wait on getRed before calling SaveBlue
    await rep.SaveRedAsync(thing);
    var b = await getBlue;  
    var c = await getGreen;

    // thing must be fully initialized before DoStuffAsync returns

    }


public class SomeThing
{
    public int ID { get; set; }
    public string Color1 { get; set; }
    public string Color2 { get; set; }
    public string Color3 { get; set; }
}

public class SomeRepository
{
    public async Task<string> GetRedAsync()
    {
        return await Task.Run(() => "red");
    }

    public async Task<string> GetBlueAsync()
    {
        return await Task.Run(() => "blue");
    }

    public async Task<string> GetGreenAsync()
    {
        return await Task.Run(() => "green");
    }

    public async Task SaveRedAsync(SomeThing thing)
    {
        // We need thing.ID here as well as other properties
        await Task.Delay(1);
    }

    public async Task SaveBlueAsync(SomeThing thing)
    {
        await Task.Delay(1);
    }

    public async Task SaveGreenAsync(SomeThing thing)
    {
        await Task.Delay(1);
    }
}

5
当您编写的代码认为可以工作但无法编译时,请包含编译错误信息。 - Jon Skeet
3个回答

8

你可以明确地使用ContinueWith - 或者你可以将每个“获取和保存”分别分解为单独的异步方法或异步lambda表达式。例如:

async Task GetAndSaveRedAsync(SomeThing thing, SomeRepository rep)
{
    var red = await rep.GetRedAsync();
    thing.Red = red;
    await SaveRedAsync(red);
    // Return red if you want (change the return type to Task<string>)
}

// Ditto for the others

然后:

protected async void DoStuffAsync()
{
    SomeThing thing = new SomeThing { ID = 5 };
    SomeRepository rep = new SomeRepository();

    var handleRed = GetAndSaveRedAsync(thing, rep);
    var handleBlue = GetAndSaveBlueAsync(thing, rep);
    var handleYellow = GetAndSaveYellowAsync(thing, rep);

    // Or use Task.WhenAll
    await handleRed;
    await handleBlue;
    await handleYellow;
}

4
你怎么知道有任何东西正在不同的线程上运行?是的,你可以使用“Task.Run”并将其放入单独的线程中,但为什么要创建比你需要的线程更多的线程呢? - Jon Skeet
那么我猜你的意思是任务不会在不同的线程上运行?我认为这是编写任务的唯一原因,以便它可以在不同的线程上异步运行。 - Rand Random
4
并不一定是这样的。它可能是,但并非必须如此。考虑一个包含async void HandleButtonClick(...){ button.Text = "Waiting"; await Task.Delay(1000); button.Text = "Waited"; }的WinForms应用程序-那里没有涉及额外的线程。你可能需要再次阅读有关async的内容 :) - Jon Skeet
谢谢Jon,我相信这是正确的方法。关于@RandRandom的观察:你基本上回避了他的问题。是的,这将在一个线程内运行另一个线程,而且它并不像简单的TPL那样简单。事实上,我越使用TAP,就越确信它不值得它的重量。它渗入了我95%的无法并行化的代码中,对于那5%可以并行化的代码,它并不像TPL那样优雅。 - user579342
3
“@user579342:‘线程内部的线程’这种概念不存在,而且我提供的代码根本不会启动任何新线程。我并没有回避RandRandom的问题,只是质疑了它的基本假设。你似乎在假定TAP是关于并行性的,但实际上它是关于异步性的,这是不同的。” - Jon Skeet
显示剩余4条评论

7

我不会混合使用ContinueWithawait,而是直接使用async lambda表达式:

protected async Task  DoStuffAsync()
{
    SomeThing thing = new SomeThing { ID = 5 };
    SomeRepository rep = new SomeRepository();

    // We need to run all three Get... methods in parallel
    // As soon as a Get... method completes we need to save the result to the correct property on thing and call the Save... method .

    Func<Task<X>> getRedFunc = async() =>
    {
        var result = await rep.GetRedAsync();
        thing.Color1 = result;
        await rep.SaveRedAsync(thing);
        return result;
    };

    var getRed = getRedFunc();

    var getBlue = rep.GetBlueAsync();
    var getGreen = rep.GetGreenAsync();

    await Task.WhenAll(getRed, getBlue, getGreen);
}

此外,请不要将async void方法用于事件处理程序以外的任何事情。您将无法观察到像这样的方法的完成情况,也可能无法处理其中可能引发的异常。


你是否有理由支持“我不会将ContinueWith和await混用”的观点? - NStuke
@NStuke,不一定按照这个顺序,但是:1)代码结构和可读性;2)当前的SynchronizationContextTaskScheduler被考虑的差异,3)异常处理和传播的差异;4)继续运行lambda的差异(人们往往忘记了ExecuteSynchronously);5)我确定我还漏掉了其他东西,但应该足够了 :) - noseratio - open to work

-1

你可以尝试并行框架:

using System.Threading;

using System.Threading.Tasks;

Func<Task<string>>[] functions = { rep.GetRedAsync, rep.GetBlueAsync, rep.GetGreenAsync }; 
Var[] GetArray = new Var[functions.Length]
int i=0;
Parallel.ForEach (var function in functions)  
{  
             GetArray[i++]=function();

}  

注意:需要 .Net 4


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