问题描述:
业务上需要在执行一个操作(需lock)之后异步发送短信通知,大致代码如下:
lock (_locker) { //其他逻辑 //异步发送短信,不等待 MessageManager.SendMessageAsync(numbers, message); }
其中,SendMessageAsync是一个静态方法,大致如下:
public static async Task<bool> SendMessageAsync(IEnumerable<string> numbers, string content) { return await Task.Factory.StartNew<bool>(() => { //send }); }
本地直接debug运行,无异常,短信发送成功。
然而更新到服务器上,在该操作执行两次左右的时候,进程崩溃重启,应用程序日志如下:
An unhandled exception occurred and the process was terminated. Application ID: /LM/W3SVC/2/ROOT Process ID: 3308 Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. StackTrace: at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext) at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext) at System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state) at System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state) at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask) --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.AwaitTaskContinuation.<ThrowAsyncIfNecessary>b__1(Object s) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch()
stackoverflow上也有人遇到过这种问题,但没有比较好的解决方案,目前我将调用发送短信方法写成如下,暂时解决问题,但着实别扭:
Task.Factory.StartNew(() =>
{
MessageManager.SendMessageAsync(numbers, message);
});
请问各位有无类似经验,谢谢!
把 await 去掉。
不能去掉的吧,需要返回,去掉的话return的是Task<bool>了
@l3oz: 你不是不需要等待吗?
//异步发送短信,不等待
MessageManager.SendMessageAsync(numbers, message);
@Launcher: 这个是个公共组件,有些调用方需要等待,我这个需求不需要
@l3oz: 是挺别扭,估计是对异步编程不熟练造成的。我建议的做法是:
bool SendMessage(xxxx,xxxx);
Task<bool> SendMessageAsync(xxxxx,xxxxx){ return Task.Factory.StartNew<bool>(() =>{return SendMessage(xxx,xxx)});}
需要等待的地方可以直接调用 SendMessage,或者:
1,await SendMessageAsync(xxx,xxx);
2, SendMessageAsync(xxx,xxx).Wait();
不需要等待的地方直接调用 SendMessageAsync(xxx,xxx);
你这里的主要问题出在你调用 SendMessageAsync(xxx,xxx) 的方法不是 async 的,上面的修改就是通过返回 Task<T> 保留可以使用 await 的空间。
@Launcher: 谢谢,明白很多!
首先async和await的确应该去掉。其次估计就是context问题了,应该可以朝这个方向研究。
感谢老赵!
建议使用SemaphoreSlim。
读一下这篇文章Best Practices in Asynchronous Programming,相信问题就能解决。
另外建议加上ConfigureAwait(false)试试:
return await Task.Factory.StartNew<bool>(() => { //send }).ConfigureAwait(false);
@dudu: 十分感谢!文章会仔细看
建议把与第三方通讯的部分和核心业务(数据处理部分)分开,lock可以锁数处理部分,但要注意数据方面的死锁问题,对于第三方通讯部分没必要去加锁限制,因为咱们也没法去决定人家的程序。