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

前言

今天上午,一個客戶反饋XX消息沒有推送到第三方連接。因而我查看了推送日誌列表,並無今天的。接着登陸服務器查詢文件日誌,看到了記錄。咱們的代碼步驟是消息先推送到消息隊列,消費消息隊列時,記錄文件日誌,而後異步推送到第三方。javascript

調試排坑

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

問題重現

構建環境

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

 
.Net FrameWork


而後ErrorSets.CommonE引入Newtonsoft.Json v11.0.2java

 
Newtonsoft.Json


而後模擬兩個推送接口,一個是用Task包裝的,一個是正常方法。git

 

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.1github

 
image.png


引入調用代碼json

 

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,加上異常捕獲app

 

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

謝謝觀看,此篇完畢。

相關文章
相關標籤/搜索