来自异步方法的 Mvc .Net 捕获异常
Posted
技术标签:
【中文标题】来自异步方法的 Mvc .Net 捕获异常【英文标题】:Mvc .Net Catch Exception from async method 【发布时间】:2016-12-17 23:37:31 【问题描述】:我正在使用 this 库作为 Mailchimp API v3.0 的包装器
现在我无法从这个库的方法中捕获异常。
这些方法是从Exception
扩展而来的MailChimpException
。
问题是,如果我从控制台应用程序运行它,那么我可以捕获异常,但是当我从我的 Web 项目运行它时,我无法捕获异常,并且代码在发生异常时卡住了。
这是我的电话示例:
public bool ListSubscribe(string emailAddress, string apiKey, string listId)
try
var api = new MailChimpManager(apiKey);
var profile = new Member();
profile.EmailAddress = emailAddress;
var output = api.Members.AddOrUpdateAsync(listId, profile).Result;
return true;
catch (Exception ex)
logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
return false;
这是我调用的库中的方法:
namespace MailChimp.Net.Logic
/// <summary>
/// The member logic.
/// </summary>
internal class MemberLogic : BaseLogic, IMemberLogic
private const string BaseUrl = "lists";
/// <summary>
/// Initializes a new instance of the <see cref="MemberLogic"/> class.
/// </summary>
/// <param name="apiKey">
/// The api key.
/// </param>
public MemberLogic(string apiKey)
: base(apiKey)
/// <summary>
/// The add or update async.
/// </summary>
/// <param name="listId">
/// The list id.
/// </param>
/// <param name="member">
/// The member.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref>
/// <name>uriString</name>
/// </paramref>
/// is null. </exception>
/// <exception cref="UriFormatException">In the .NET for Windows Store apps or the Portable Class Library, catch the base class exception, <see cref="T:System.FormatException" />, instead.<paramref name="uriString" /> is empty.-or- The scheme specified in <paramref name="uriString" /> is not correctly formed. See <see cref="M:System.Uri.CheckSchemeName(System.String)" />.-or- <paramref name="uriString" /> contains too many slashes.-or- The password specified in <paramref name="uriString" /> is not valid.-or- The host name specified in <paramref name="uriString" /> is not valid.-or- The file name specified in <paramref name="uriString" /> is not valid. -or- The user name specified in <paramref name="uriString" /> is not valid.-or- The host or authority name specified in <paramref name="uriString" /> cannot be terminated by backslashes.-or- The port number specified in <paramref name="uriString" /> is not valid or cannot be parsed.-or- The length of <paramref name="uriString" /> exceeds 65519 characters.-or- The length of the scheme specified in <paramref name="uriString" /> exceeds 1023 characters.-or- There is an invalid character sequence in <paramref name="uriString" />.-or- The MS-DOS path specified in <paramref name="uriString" /> must start with c:\\.</exception>
/// <exception cref="MailChimpException">
/// Custom Mail Chimp Exception
/// </exception>
/// <exception cref="TargetInvocationException">The algorithm was used with Federal Information Processing Standards (FIPS) mode enabled, but is not FIPS compatible.</exception>
/// <exception cref="ObjectDisposedException">
/// The object has already been disposed.
/// </exception>
/// <exception cref="EncoderFallbackException">
/// A fallback occurred (see Character Encoding in the .NET Framework for complete explanation)-and-<see cref="P:System.Text.Encoding.EncoderFallback"/> is set to <see cref="T:System.Text.EncoderExceptionFallback"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Enlarging the value of this instance would exceed <see cref="P:System.Text.StringBuilder.MaxCapacity"/>.
/// </exception>
/// <exception cref="FormatException">
/// <paramref>
/// <name>format</name>
/// </paramref>
/// includes an unsupported specifier. Supported format specifiers are listed in the Remarks section.
/// </exception>
public async Task<Member> AddOrUpdateAsync(string listId, Member member)
using (var client = this.CreateMailClient($"BaseUrl/"))
var response =
await
client.PutAsJsonAsync($"listId/members/this.Hash(member.EmailAddress.ToLower())", member, null).ConfigureAwait(false);
await response.EnsureSuccessMailChimpAsync().ConfigureAwait(false);
return await response.Content.ReadAsAsync<Member>().ConfigureAwait(false);
有谁知道为什么我能够在控制台应用程序中触发异常,但在 Web 项目中却不能。有什么建议检查什么?
更新:
如果我将代码更改为使用Task.Run
,例如:
var task = Task.Run(async () => await api.Members.AddOrUpdateAsync(listId, profile); );
task.Wait();
所以:
public bool ListSubscribe(string emailAddress, string apiKey, string listId)
try
var api = new MailChimpManager(apiKey);
var profile = new Member();
profile.EmailAddress = emailAddress;
var task = Task.Run(async () => await api.Members.AddOrUpdateAsync(listId, profile); );
task.Wait();
return true;
catch (Exception ex)
logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
return false;
然后我在 catch 块中捕获异常,但是当一切正常时我如何从任务中获取结果???
【问题讨论】:
既然AddOrUpdateAsync()
被标记为async
,你不需要用await
来调用它吗?例如var output = await api.Members.AddOrUpdateAsync(listId, profile);
@JasonEvans 但是我必须将我的方法设置为异步,我不希望这样。我需要结果并且使用 .Result 它正在工作。我是 C# 新手,所以也许我遗漏了什么??
@carpics - 在您的调试器中检查您的 output
变量的类型,这应该会提示您出了什么问题
如果您摆脱了这些可怕的空 catch 块,您将向可靠的解决方案迈出一大步。
你没有!你让它“冒泡”直到你真正有能力和上下文处理的有意义的位置。 E.g. in your global.asax's error handler.
【参考方案1】:
适当的解决方案是让这个“一直异步”,正如我在 async best practices 上的 MSDN 文章中所描述的那样。直接调用Result
的问题在于它会导致死锁(正如我在我的博客文章Don't block on async code 中所描述的那样)。 (在您的情况下,Result
的第二个问题是它将MailChimpException
包装在AggregateException
中,使错误处理更加尴尬)。
包装对Task.Run
的调用,然后调用Result
是一种降低Web 服务可扩展性的技巧(也不会处理异常包装的次要问题)。如果您不想使用异步代码,请使用同步 API。由于这是一个 I/O 操作,最自然的方法是异步代码,如下所示:
public async Task<bool> ListSubscribeAsync(string emailAddress, string apiKey, string listId)
try
var api = new MailChimpManager(apiKey);
var profile = new Member();
profile.EmailAddress = emailAddress;
var output = await api.Members.AddOrUpdateAsync(listId, profile);
return true;
catch (Exception ex)
logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
return false;
【讨论】:
好吧,因为我没有真正体验过 c#,我的问题是:如果我让它“一直异步”,那么我最终如何得到结果。我现在希望此方法返回订阅 Mailchimp 的“会员”。该成员在执行后从任务中返回。那么如果我让它“一直异步”我如何得到结果?有些方法我调用只是为了从 Mailchimp 中获取一些东西 @carpics:你使用await
来获取异步方法的结果。
确实如此。这就是为什么我让它“一直异步”,所有调用者方法都需要是异步的。但正如我已经说过的,这对我来说目前是不可能的。我正在更新使用“PerceptiveMcApi”作为包装器的现有项目。但这不支持新的 Mailchimp API v3.0。唯一支持的 Mvc.Net 包装器是这个带有异步方法的包装器。所以在我的项目中,我从很多地方调用这些方法,比如 ListSubscribe(),我需要将很多其他方法作为异步。我需要正确地从同步方法调用 asnyc 方法
@carpics:没有合适的方法来进行异步同步。只有各种各样的hack,所有这些都是脆弱的。如果你不能一直异步,那么我建议你一直同步。
@StephenCleary 有大量只提供异步方法的库,以及大量现有代码库无法切换到完全异步的人。似乎默认答案不能只是 - 嘿,您如何重新编写整个应用程序。【参考方案2】:
这是对更新后问题的回答。
您只需在可能引发异常的任务上调用Task.Result
即可获得结果。您的 lambda 表达式必须使用 return await
返回被调用方法的 Task<T>
结果。否则整个表达式的结果将是一个非泛型任务,它表示一个不返回值且没有Result
成员的操作。
static void Main(string[] args)
try
var task = Task.Run(async () => return await AsyncMethod(); );
Console.WriteLine("Result: " + task.Result);
catch
Console.WriteLine("Exception caught!");
Console.ReadKey();
static async Task<string> AsyncMethod()
throw new ArgumentException();
var result = await Task.Run(() => return "Result from AsyncMethod"; );
return result;
请注意,我已删除对 Task.Wait
的调用,因为当您还获取结果时,它是不必要的。
引用自 MSDN 的 Task<TResult>.Result
参考页面:
访问属性的 get 访问器会阻塞调用线程,直到 异步操作完成;相当于调用 等待方法。
代码 sn-p 将显示来自catch
块的文本。如果你从AsyncMethod
中删除第一行,你会得到显示的结果。
【讨论】:
【参考方案3】:这个:https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/
长话短说,您永远不会访问异步方法的结果,也永远不会看到异常。如果不想让父方法异步,则需要从 AddOrUpdateAsync 方法返回的任务对象中显式获取.Result
属性的值。
【讨论】:
抱歉,我更新了上面的代码。我实际上是通过 .Result 获得价值,只是不知何故我在问题的代码中忘记了这一点以上是关于来自异步方法的 Mvc .Net 捕获异常的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET MVC 控制器不会从 LINQ lambda 捕获异常