• .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:Usershp.nugetpackagesmicrosoft.netcore.app2.1.0
    ef
    etcoreapp2.1System.Runtime.dll
    #endregion
    

    打开srcSystem.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;我打开了另外一个srcSystem.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

    谢谢观看,此篇完毕。

  • 相关阅读:
    vcruntime140.dll 14.0与PHP版本不兼容,PHP Warning: 'vcruntime140.dll' 14.0 is not compatible with this PHP build linked with 14.16 in Unknown on line 0
    PHP处理字符中的emoji表情
    Thinkphp5 使用unlink删除文件出错Permission denied
    TP5多字段排序
    TP5 按照汉字的拼音排序
    PHP发送微信模版消息
    [52ABP系列]
    [52ABP系列]
    通过微信公众号实现微信快捷登陆
    [Jexus系列] 一、安装并运行 Jexus
  • 原文地址:https://www.cnblogs.com/fancunwei/p/9706723.html
Copyright © 2020-2023  润新知