【问题标题】:C# ContinueWith / ReturnC# ContinueWith / 返回
【发布时间】:2015-07-25 12:24:23
【问题描述】:

我对 C# 中的异步编程有点陌生,正在努力应对一个小而令人沮丧的挑战。

我有一个运行良好的 ASP.Net MVC WEB API。另一方面,我创建了这个 WEB API 客户端方法:

public async Task<DTOVArt> GetVArtFormArt(DTOArt art)
    {
        using (var client = GetHttpClient())
        {
            var response = await client.GetAsync("api/APIVArt/Get/" + art.ART_ID);
            if (!response.IsSuccessStatusCode) return null;
            var arts = await response.Content.ReadAsAsync<DTOVArt>();
            return arts;
        }
    }

这似乎工作得很好......

这个方法是从 WPF 视图模型中调用的,这就是我的问题所在..

private DTOVObs TransFormAaretsGang(DTOAaretsGang aaretsGang)
    {
        var dtovObs = new DTOVObs();

        using (var artservice = new ArtService())
        {
            artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID}).ContinueWith(t =>
            {
                dtovObs.Familie_id = t.Result.Familie_id;
                dtovObs.Gruppe_id = t.Result.Gruppe_id;
                dtovObs.ART_ID = t.Result.ART_ID;
                if (aaretsGang.Dato != null) dtovObs.Aarstal = aaretsGang.Dato.Value.Year;
                return dtovObs;
            });
        }

        return dtovObs;
    }

问题是最后一个方法在到达 ContinueWith 语句块之前执行 return 语句,这实际上设置了应该返回的类中的值。

任何尝试执行任何类型的 Wait() 或使用 .Result 而不是 ContinueWith 只会阻止一切。

如果我在 ContinueWith 块内执行 return,C# 编译器会说该方法缺少 return 语句,这是真的。

【问题讨论】:

  • 在不使用 await 关键字的情况下调用 async 方法很危险。如果您不在Task 上调用Result,将在其他地方执行,如果您调用Result,您的应用程序通常会死锁。最简单的解决方案是只使用await,并将调用方法重构为async,并同样使用await,直到您使用事件处理程序。

标签: c# asynchronous


【解决方案1】:

这就是异步的本质。因为你的调用是异步的,所以稍后会执行,下面的代码只是继续执行。

尝试添加一个await,如果这是调用的root,则删除ContinueWith,通常它是一个事件处理程序:

private async Task<DTOVObs> TransFormAaretsGang(DTOAaretsGang aaretsGang)
{
    var dtovObs = new DTOVObs();
    DTOVArt Result = await artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID});
    dtovObs.Familie_id = Result.Familie_id;
    dtovObs.Gruppe_id = Result.Gruppe_id;
    dtovObs.ART_ID = Result.ART_ID;
    if (aaretsGang.Dato != null) 
       dtovObs.Aarstal = aaretsGang.Dato.Value.Year;
    return dtovObs;
}

如果您仍想返回一个异步任务,以便任何调用此方法的调用者都可以等待结果,请尝试:

private async Task<DTOVObs> TransFormAaretsGang(DTOAaretsGang aaretsGang)
    {
        using (var artservice = new ArtService())
        {
            return artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID}).ContinueWith(t =>
            {
                dtovObs.Familie_id = t.Result.Familie_id;
                dtovObs.Gruppe_id = t.Result.Gruppe_id;
                dtovObs.ART_ID = t.Result.ART_ID;
                if (aaretsGang.Dato != null) dtovObs.Aarstal = aaretsGang.Dato.Value.Year;
                return dtovObs;
            });
        }
    }

【讨论】:

  • 我已经尝试过这个解决方案,但只是导致整个应用程序锁定在 WEB API 调用上。
  • @ThomasE:试试await client.GetAsync("api/APIVArt/Get/" + art.ART_ID).ConfigureAwait(false);await artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID}).ConfigureAwait(false);
  • @ThomasE:这个想法是使用ConfigureAwait(false); 来避免捕获 UI 线程上下文。我对 Wpf 不熟悉,我想你可以看看这个以获得更好的方法:msdn.microsoft.com/en-us/magazine/dn605875.aspx
  • 你的第二个代码 sn-p 将在它有机会完成 HTTP 请求之前处理 ArtService,因为你没有等待任何东西。
  • Stephen Cleary - 非常感谢您的文章 - 现在真的很棒。
【解决方案2】:

当您使用 async / await 时,您不必再使用 ContinueWith。 ContinueWith 的意思是:等上一个完成,然后用结果做下一个。

async await 会为你做这件事。

假设你有一个异步函数。如果返回类型为 TResult,则所有异步函数都返回 Task(用于 void 返回)或 Task&lt;TResult>

private async Task<int> SlowAdditionAsync(int a, int b)
{
    await Task.Delay(TimeSpan.FromSeconds(5); // causing the slow part
    return a + b;
}

用法:

private async Task PerformSlowAddition()
{
    int a = ...;
    int b = ...;
    int x =await SlowAditionAsync(a, b);
    // the statement after the await, the task is finished, the result is in x.
    // You can used the result as if you used Continuewith:
    DisplayAddition(x);
}

或者如果你想在计算过程中做其他事情:

private async Task PerformSlowAddition()
{
    int a = ...;
    int b = ...;
    var taskSlowAddition = SlowAditionAsync(a, b);
    DoSomethingElse(); // while the calculator does its thing
    // now we need the result:
    int x = await taskSlowAddition;
    // no need to use ContinueWith, the next statement will be executed:
    DisplayAddition(x);
}

记住:

  • 所有使用返回任务或任务的函数的函数都应声明为异步
  • 所有异步函数都返回 Task,如果它们返回 TResult,则返回 void 或 Task。
  • Task 返回有一个例外:事件处理程序返回 void
  • 调用异步函数后,可以做其他事情。
  • 当你需要结果时使用 await
  • 您只能等待一个任务或一个任务
  • await Task 的值为 TResult

只有一个异步函数不需要返回任务,那就是事件处理程序:

private async void OnButton1_Clicked(object sender, ...)
{
    var taskX = DosomethingAsync(...)
    DoSomethingElse();'
    // now we need the result of taskX:
    var x = await TaskX;
    ProcessReault(x)
}

请注意,虽然事件处理程序没有返回任务,但它仍然是异步的

如果您有一些语句需要在后台运行,而您的用户界面保持响应,请使用 Task.Factory.StartNew() 或更现代的 Task.Run():

private int SlowCalculation(int a, int b)
{
    // do something really difficult and slow
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a + b;
}

// make it async:
private async Task<int> SlowCalculationAsync(int a, int b)
{
    return await Task.Run( () => SlowCalculation(a, b));
}

用法:

private async Task CalculateAsync()
{
    int a = ...;
    int b = ...;
    int x = await SlowCalculationAsync(a, b);
    Display(x);
}

private async void OnButton1_clicked(object sender, ...)
{
     await CalculateAsync();
}

【讨论】:

    猜你喜欢
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-08
    相关资源
    最近更新 更多