gpt4 book ai didi

c# - 使用异步调用时异常处理的良好模式

转载 作者:行者123 更新时间:2023-11-30 16:13:00 25 4
gpt4 key购买 nike

我想使用 Web API,我看到很多人推荐 System.Net.Http.HttpClient .

没关系...但我只有 VS-2010,所以我不能使用 async/await刚刚。相反,我想我可以使用 Task<TResult>结合ContinueWith .所以我尝试了这段代码:

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));

client.GetStringAsync(STR_URL_SERVER_API_USERS).ContinueWith(task =>
{
var usersResultString = task.Result;
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(usersResultString);
});

我的第一个观察是意识到如果 URL 不可用它不会产生任何错误,但也许会有更多这样的错误......

所以我试图找到一种方法来处理此类异步调用(特别是 HttpClient)的异常。我注意到“任务”有 IsFaulted属性(property)和一个AggregateException也许可以使用,但我不确定如何使用。

另一个观察结果是 GetStringAsync返回 Task<string> ,但是 GetAsync返回 Task<HttpResponseMessage> .后者可能更有用,因为它提供了一个 StatusCode。 .

您能否分享一个关于如何正确使用异步调用并以良好方式处理异常的模式?基本解释也将不胜感激。

最佳答案

我不会对成功和错误的场景使用单独的 ContinueWith 延续。我宁愿使用 try/catch 在一个地方处理这两种情况:

task.ContinueWith(t =>
{
try
{
// this would re-throw an exception from task, if any
var result = t.Result;
// process result
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
lbUsers.Clear();
lbUsers.Items.Add("Error loading users!");
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);

如果 t 是非通用 Task(而不是 Task<TResult> ),您可以执行 t.GetAwaiter().GetResult() 以重新抛出 ContinueWith lambda 中的原始异常; t.Wait() 也可以。准备好处理 AggregatedException ,你可以通过这样的方式进入内部异常:

catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;

MessageBox.Show(ex.Message);
}

如果您正在处理一系列 ContinueWith ,通常您不必在 each ContinueWith 中处理异常。对最外层的结果任务执行一次,例如:

void GetThreePagesV1()
{
var httpClient = new HttpClient();
var finalTask = httpClient.GetStringAsync("http://example.com")
.ContinueWith((task1) =>
{
var page1 = task1.Result;
return httpClient.GetStringAsync("http://example.net")
.ContinueWith((task2) =>
{
var page2 = task2.Result;
return httpClient.GetStringAsync("http://example.org")
.ContinueWith((task3) =>
{
var page3 = task3.Result;
return page1 + page2 + page3;
}, TaskContinuationOptions.ExecuteSynchronously);
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap()
.ContinueWith((resultTask) =>
{
httpClient.Dispose();
string result = resultTask.Result;
try
{
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}

当您访问内部任务 (ContinueWith) 的结果时,内部任务中抛出的任何异常都将传播到最外层的 taskN.Result lambda。

这段代码很实用,但也很丑陋且不可读。 JavaScript 开发人员称它为厄运的回调金字塔。他们有 Promises 来处理它。 C# 开发人员有 async/await ,不幸的是,由于 VS2010 的限制,您无法使用它。

IMO,TPL 中最接近 JavaScript Promises 的是 Stephen Toub's Then pattern。与 C# 4.0 中的 async/await 最接近的是来自同一篇博文的 Iterate 模式,它使用了 C# yield 特性。

使用 Iterate 模式,可以以更具可读性的方式重写上面的代码。请注意,在 GetThreePagesHelper 中,您可以使用所有熟悉的同步代码语句,如 usingforwhiletry/catch 等。但是,了解此模式的异步代码流很重要:

void GetThreePagesV2()
{
Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) =>
{
try
{
var lastTask = (Task<string>)iteratorTask.Result;

var result = lastTask.Result;
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}

IEnumerable<Task> GetThreePagesHelper()
{
// now you can use "foreach", "using" etc
using (var httpClient = new HttpClient())
{
var task1 = httpClient.GetStringAsync("http://example.com");
yield return task1;
var page1 = task1.Result;

var task2 = httpClient.GetStringAsync("http://example.net");
yield return task2;
var page2 = task2.Result;

var task3 = httpClient.GetStringAsync("http://example.org");
yield return task3;
var page3 = task3.Result;

yield return Task.Delay(1000);

var resultTcs = new TaskCompletionSource<string>();
resultTcs.SetResult(page1 + page1 + page3);
yield return resultTcs.Task;
}
}

/// <summary>
/// A slightly modified version of Iterate from
/// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
/// </summary>
public static Task<Task> Iterate(IEnumerable<Task> asyncIterator)
{
if (asyncIterator == null)
throw new ArgumentNullException("asyncIterator");

var enumerator = asyncIterator.GetEnumerator();
if (enumerator == null)
throw new InvalidOperationException("asyncIterator.GetEnumerator");

var tcs = new TaskCompletionSource<Task>();

Action<Task> nextStep = null;
nextStep = (previousTask) =>
{
if (previousTask != null && previousTask.Exception != null)
tcs.SetException(previousTask.Exception);

if (enumerator.MoveNext())
{
enumerator.Current.ContinueWith(nextStep,
TaskContinuationOptions.ExecuteSynchronously);
}
else
{
tcs.SetResult(previousTask);
}
};

nextStep(null);
return tcs.Task;
}

关于c# - 使用异步调用时异常处理的良好模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22274924/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com