.Net版本依賴之坑引發的搜查


前言

今天上午,一個客戶反饋XX消息沒有推送到第三方鏈接。於是我查看了推送日志列表,並沒有今天的。接着登錄服務器查詢文件日志,看到了記錄。我們的代碼步驟是消息先推送到消息隊列,消費消息隊列時,記錄文件日志,然后異步推送到第三方。

調試排坑

經過一番寒徹骨的查詢幾個關鍵表,構造數據,並調試推送后,發現了問題源頭,是Json版本依賴問題引發的坑,然后修改版本號發布解決了。下面讓我們用一個簡易的Demo重現下問題所在。

問題重現

構建環境

首先新建基於.NetFrameWork 4.5.1版本的類庫ErrorSets.CommonE和控制台程序ConsoleApp1

 
.Net FrameWork


然后ErrorSets.CommonE引入Newtonsoft.Json v11.0.2

 
Newtonsoft.Json


然后模擬兩個推送接口,一個是用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

 
image.png


引入調用代碼

 

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)時,結果令我們失望。既沒有報錯,也沒有執行方法體內輸出。

 
image.png


改動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>

再運行程序,結果如下。達到我們預期

 

 
image.png

 

為了更直觀的顯示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,出現如下結果:

 
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

謝謝觀看,此篇完畢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM