前言
今天上午,一個客戶反饋XX消息沒有推送到第三方鏈接。於是我查看了推送日志列表,並沒有今天的。接着登錄服務器查詢文件日志,看到了記錄。我們的代碼步驟是消息先推送到消息隊列,消費消息隊列時,記錄文件日志,然后異步推送到第三方。
調試排坑
經過一番寒徹骨的查詢幾個關鍵表,構造數據,並調試推送后,發現了問題源頭,是Json版本依賴問題引發的坑,然后修改版本號發布解決了。下面讓我們用一個簡易的Demo重現下問題所在。
問題重現
構建環境
首先新建基於.NetFrameWork 4.5.1版本的類庫ErrorSets.CommonE和控制台程序ConsoleApp1
然后ErrorSets.CommonE引入Newtonsoft.Json v11.0.2
然后模擬兩個推送接口,一個是用Task包裝的,一個是正常方法。
public class SeriEx {
public static void TaskPostThird(object obj) {
Task.Factory.StartNew(() =>
{
var data = JsonConvert.SerializeObject(obj);
Console.WriteLine($"TaskPostThird:{data}");
});
}
public static void PostThird(object obj) {
var data = JsonConvert.SerializeObject(obj);
Console.WriteLine($"PostThird:{data}");
}
}
ConsoleApp1引入Newtonsoft.Json v9.0.1
引入調用代碼
namespace ConsoleApp1
{
class Program {
static void Main(string[] args) {
var user = new User() { Mobile = "12546423" };
// SeriEx.PostThird(user);
SeriEx.TaskPostThird(user);
Console.WriteLine("End");
Console.ReadKey();
}
}
}
查看運行結果
當Main調用SeriEx.TaskPostThird(user)時,結果令我們失望。既沒有報錯,也沒有執行方法體內輸出。
改動TaskPostThird,加上異常捕獲
public static void TaskPostThird(object obj)
{
Task.Factory.StartNew(() =>
{
try
{
var data = JsonConvert.SerializeObject(obj);
Console.WriteLine($"TaskPostThird:{data}");
}
catch (Exception ex) {
Console.WriteLine($"TaskPostThirdEx:{ex.Message}");
}
});
}
運行程序后,依然沒有異常拋出,也沒有任何結果輸出。讓我們將Main調用改成不帶Task的SeriEx.PostThird(user)
class Program {
static void Main(string[] args) {
var user = new User() { Mobile = "12546423" };
SeriEx.PostThird(user);
Console.WriteLine("End");
Console.ReadKey();
}
}
執行結果如下,拋出了異常。結果符合預期,只要不是沉默的代碼就好!
根據異常提示,我們在app.config追加如下配置oldVersion改成0.0.0.0-11.0.0.0,支持不同版本。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
再運行程序,結果如下。達到我們預期
為了更直觀的顯示Task內部異常問題,我們用下面代碼:
Task.Factory.StartNew(() =>
{
throw new Exception("Exxx");
});
運行結果毫無反應,可以猜測Task.Factory內部屏蔽了異常?
NetCore下是否同樣問題?
按照相同的套路,建一個基於.netCore2.1的類庫ErrorSets.CommonCore和控制台程序ConsoleAppCore,並引入不同版本的Newtonsoft.Json。編譯結果如下:
我們可以看到.NetCore版本直接提示報錯,編譯失敗。讓我們來繼續測試下另外一個問題Task.Factory.StartNew下異常問題。為了編譯通過,先移除json。
static void Main(string[] args) {
var user = new User() { Mobile = "12546423" };
TaskThrowExcetption();
Console.ReadKey();
}
static void TaskThrowExcetption() {
Task.Factory.StartNew(() =>
{
throw new Exception("s");
});
}
運行結果一片空白,並沒有跑錯。說明.NetCore下也是屏蔽了異常。
中斷的源碼路
根據F12提示,看到Task引用的是System.Runtime.dll,所以我下載了corefx
#region 程序集 System.Runtime, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Users\hp\.nuget\packages\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Runtime.dll
#endregion
打開src\System.Runtime,解決方案搜索Factory,迎接我的是
public partial class TaskFactory {
public TaskFactory() { }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<object, TResult> function, object state) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<object, TResult> function, object state, System.Threading.CancellationToken cancellationToken) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<object, TResult> function, object state, System.Threading.CancellationToken cancellationToken, System.Threading.Tasks.TaskCreationOptions creationOptions, System.Threading.Tasks.TaskScheduler scheduler) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<object, TResult> function, object state, System.Threading.Tasks.TaskCreationOptions creationOptions) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<TResult> function) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<TResult> function, System.Threading.CancellationToken cancellationToken) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<TResult> function, System.Threading.CancellationToken cancellationToken, System.Threading.Tasks.TaskCreationOptions creationOptions, System.Threading.Tasks.TaskScheduler scheduler) { throw null; }
public System.Threading.Tasks.Task<TResult> StartNew<TResult>(System.Func<TResult> function, System.Threading.Tasks.TaskCreationOptions creationOptions) { throw null; }
}
令人失望的partial,令人失望的throw null;我打開了另外一個src\System.Threading.Tasks,看到了項目一片空白!!!
Bing下生物
一片迷茫之下,我使用了Bing,國際版搜索“task.factory.startnew sourcecode”,第一條是https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,結果讓我驚喜!
在微軟的搜索框內,我輸入TaskFactory,出現如下結果:
public Task StartNew(Action action) {
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task currTask = Task.InternalCurrent;
return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask),
m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
}
Task.InternalStartNew如下:
internal static Task InternalStartNew( Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) {
// Validate arguments.
if (scheduler == null)
{
throw new ArgumentNullException("scheduler");
}
Contract.EndContractBlock();
// Create and schedule the task. This throws an InvalidOperationException if already shut down.
// Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
t.PossiblyCaptureContext(ref stackMark);
t.ScheduleAndStart(false);
return t;
}
查到這里,我累了。工作到此結束。。。
微軟.net源碼鏈接已收藏到.NetCore外國一些高質量博客分享,保持長期更新
源碼
源碼查看問題記錄集,歡迎 Star
總結
今日發現了三個問題如下:
- Task.Factory.StartNew方法體內不會拋出異常【原因:主線程默認不捕獲異步線程的異常】。
- 針對json不同版本,.net framework可以編譯通過,.netcore編譯失敗。
- .net framework可以通過配置文件解決版本問題,那么.netcore是如何解決的?
今天最重要的是發現了微軟.net源碼網址,不在github,在他自己的老家https://referencesource.microsoft.com - 2018-10-19更新:referencesource的github地址: https://github.com/Microsoft/referencesource
謝謝觀看,此篇完畢。