gpt4 book ai didi

c# - 当线程已经终止(.NET 5/Core)时,Thread.Join 方法并不总是返回相同的值

转载 作者:行者123 更新时间:2023-12-03 12:43:42 30 4
gpt4 key购买 nike

Thread.Join方法有三个重载:Join() , Join(Int32)Join(TimeSpan) .对于这三个重载中的每一个,都有以下语句 in the Microsoft doc :

If the thread has already terminated when Join is called, the method returns immediately.


虽然此声明对 Join() 有意义重载,它没有指定为 Join(Int32) 返回哪个值和 Join(TimeSpan)的,所以我测试了 Int32在两种不同的环境中过载:
  • Windows 10:返回 真实
  • Linux/Docker:返回 (在 Docker 桌面上使用 mcr.microsoft.com/dotnet/runtime:5.0)

  • 请注意,Linux/Docker 实现返回 真实 (像 Windows 一样)如果线程在 Join 时仍在运行被调用并在调用后终止。它只返回 如果线程在调用之前已经终止。
    在我看来 Join无论平台如何,都应该始终返回 true,那么如何解释这种不一致的行为呢?我错过了什么还是.NET 5 错误?
    更新
    正如@txtechhelp 所建议的, here is a .NET Fiddle使用我正在测试的确切代码。
    如果我在 Windows 10(或 .NET Fiddle)上运行此代码,我会得到以下结果:
    Starting..
    Sleeping 1200..expect T1 end before join
    In T1
    Leaving T1
    Join(100)..expect success
    Join(100) success!
    Done..
    然后,如果我使用 mcr.microsoft.com/dotnet/runtime:5.0 运行此代码在 Docker Desktop (v. 3.1.0) 上,我得到以下结果:
    Starting..
    Sleeping 1200..expect T1 end before join
    In T1
    Leaving T1
    Join(100)..expect success
    Join(100) failed
    Done..
    更新 2
    实际上,经过进一步测试后,我意识到上面的测试只有在我调用 Join 时才会失败。当 Docker 应用程序正在卸载时(即接收到 AssemblyLoadContext.Default.Unloading 事件,这是 Docker 发送的通知它将关闭应用程序的信号)。
    所以 here is the exact test这甚至在 .NET Fiddle 上都失败了:
    public class Program
    {
    public static void Main()
    {
    System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += (arg) => { OnStopSignalReceived("application unloading"); };
    }

    public static void T1()
    {
    System.Console.WriteLine("In T1");
    System.Threading.Thread.Sleep(1000);
    System.Console.WriteLine("Leaving T1");
    }

    private static void OnStopSignalReceived(string stopSignalSource)
    {
    System.Threading.Thread t1 = new System.Threading.Thread(T1);
    System.Console.WriteLine("Starting..");
    t1.Start();
    System.Console.WriteLine("Sleeping 1200..expect T1 end before join");
    System.Threading.Thread.Sleep(1200);
    System.Console.WriteLine("Join(100)..expect success");
    if (t1.Join(100))
    {
    System.Console.WriteLine("Join(100) success!");
    }
    else
    {
    System.Console.WriteLine("Join(100) failed");
    }
    t1.Join();
    System.Console.WriteLine("Done..");
    }
    }

    最佳答案

    此问题似乎是 AssemblyLoadContext class 的底层 .NET 5 代码中的错误。或者可能是文档尚未指定的一些未定义行为;也就是说, AssemblyLoadContext.Unloading event 的文档只是说:

    Occurs when the AssemblyLoadContext is unloaded.


    鉴于您遇到的问题,这是唯一的句子,并没有提供太多的上下文。
    话虽如此,经过一番挖掘,我编写了您提供的代码的 2 个版本,并发现了一些处理 AssemblyLoadContext.Unloading 的有趣行为。和线程。
    此代码重现了您提到的错误:
    buggy .cs
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Runtime.Loader;

    public class Program
    {
    static Thread t1 = new Thread(ThreadFn);
    static Stopwatch sw = new Stopwatch();

    public static void Main()
    {
    AssemblyLoadContext.Default.Unloading += ContextUnloading;
    sw.Start();
    Console.WriteLine("{0}ms: leaving main", sw.ElapsedMilliseconds);
    }

    public static void ThreadFn()
    {
    Console.WriteLine("{0}ms: in ThreadFn, sleeping 1s", sw.ElapsedMilliseconds);
    Thread.Sleep(1000);
    Console.WriteLine("{0}ms: leaving ThreadFn", sw.ElapsedMilliseconds);
    }

    private static void ContextUnloading(AssemblyLoadContext context)
    {
    Console.WriteLine("{0}ms: unloading '{1}', thread state '{2}'", sw.ElapsedMilliseconds, context, t1.ThreadState);

    // possible bug/UB with t1.Start() in this function
    Console.WriteLine("{0}ms: starting thread", sw.ElapsedMilliseconds);
    t1.Start();

    Console.WriteLine("{0}ms: calling Sleep(1200); expect thread in state '{1}' to end before join called", sw.ElapsedMilliseconds, t1.ThreadState);
    Thread.Sleep(1200);
    Console.WriteLine("{0}ms: calling Join(100) on thread in state '{1}'; expect 'succeeded!'", sw.ElapsedMilliseconds, t1.ThreadState);
    Console.WriteLine("Join(100) {0}", (t1.Join(100) ? "succeeded!" : "failed"));
    Console.WriteLine("{0}ms: done", sw.ElapsedMilliseconds);
    }
    }
    Run it in dotnetfiddle
    运行该代码会给我以下结果:
    0ms: leaving main
    12ms: unloading '"Default" System.Runtime.Loader.DefaultAssemblyLoadContext #0', thread state 'Unstarted'
    13ms: starting thread
    14ms: calling Sleep(1200); expect thread in state 'Running' to end before join called
    14ms: in ThreadFn, sleeping 1s
    1014ms: leaving ThreadFn
    1214ms: calling Join(100) on thread in state 'Stopped'; expect 'succeeded!'
    Join(100) failed
    1214ms: done
    您会注意到在这个有缺陷的版本中,每个点的线程状态和时间间隔都与提供的代码相匹配。该错误发生在 Join(Int32) 方法被调用;即使文档说明返回值为 Boolean其中值为:

    true if the thread has terminated; false if the thread has not terminated after the amount of time specified by the millisecondsTimeout parameter has elapsed.


    鉴于线程是 Stopped ,根据 ThreadState 文档,表示线程要么响应了 Abort调用(在上面的代码中没有调用),或者如果

    A thread is terminated.


    并阅读文档 Understanding System.Runtime.Loader.AssemblyLoadContext , 他们甚至会记下

    Be aware of thread races. Loading can be triggered by multiple threads. The AssemblyLoadContext handles thread races by atomically adding assemblies to its cache. The race loser's instance is discarded. In your implementation logic, don't add extra logic that doesn't handle multiple threads properly.


    结合所有这些,我们假设调用 Join(Int32)应该给出 true 的预期结果在上面的代码中。
    所以,是的,这似乎是一个错误。
    然而
    如果将线程开始移动到 Main函数,而不是在卸载事件处理程序中, AssemblyLoadContext.UnloadingDefault在线程完成并调用 Join(Int32) 之前不会调用上下文。然后,当然,返回预期的结果。 Unloading 是有道理的直到线程完成后才会调用事件,因为它可以被认为是当前程序集上下文的“一部分”,但它没有解释为什么上面代码中的错误仍然发生。
    所以虽然 Join(100)调用确实在下面的代码中按预期成功,这似乎是因为 AssemblyLoadContext.UnloadingMain 之后不会被调用如预期的那样退出,而是在线程完成后调用它,这具有上下文意义,但不一定在任何文档中注明。
    “成功”代码:
    同步错误.cs
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Runtime.Loader;

    public class Program
    {
    static Thread t1 = new Thread(ThreadFn);
    static Stopwatch sw = new Stopwatch();

    public static void Main()
    {
    AssemblyLoadContext.Default.Unloading += ContextUnloading;
    sw.Start();

    // Get expected result starting thread, but Unloading isn't called until AFTER the thread
    // finishes, which is not the expected result according to the .NET documentation
    Console.WriteLine("{0}ms: starting thread", sw.ElapsedMilliseconds);
    t1.Start();

    Console.WriteLine("{0}ms: leaving main", sw.ElapsedMilliseconds);
    }

    public static void ThreadFn()
    {
    Console.WriteLine("{0}ms: in ThreadFn, sleeping 1s", sw.ElapsedMilliseconds);
    Thread.Sleep(1000);
    Console.WriteLine("{0}ms: leaving ThreadFn", sw.ElapsedMilliseconds);
    }

    private static void ContextUnloading(AssemblyLoadContext context)
    {
    Console.WriteLine("{0}ms: unloading '{1}', thread state '{2}'", sw.ElapsedMilliseconds, context, t1.ThreadState);
    Console.WriteLine("{0}ms: calling Sleep(1200); expect thread in state '{1}' to end before join called", sw.ElapsedMilliseconds, t1.ThreadState);
    Thread.Sleep(1200);
    Console.WriteLine("{0}ms: calling Join(100) on thread in state '{1}'; expect 'succeeded!'", sw.ElapsedMilliseconds, t1.ThreadState);
    Console.WriteLine("Join(100) {0}", (t1.Join(100) ? "succeeded!" : "failed"));
    Console.WriteLine("{0}ms: done", sw.ElapsedMilliseconds);
    }
    }
    Run it in dotnetfiddle
    运行该代码会给我以下结果:
    0ms: starting thread
    11ms: leaving main
    12ms: in ThreadFn, sleeping 1s
    1012ms: leaving ThreadFn
    1013ms: unloading '"Default" System.Runtime.Loader.DefaultAssemblyLoadContext #0', thread state 'Stopped'
    1014ms: calling Sleep(1200); expect thread in state 'Stopped' to end before join called
    2214ms: calling Join(100) on thread in state 'Stopped'; expect 'succeeded!'
    Join(100) succeeded!
    2215ms: done
    您会注意到 Unload直到线程完成后才会调用事件。
    在撰写此答案时,有 82 open bugs for the AssemblyLoadContext class346 closed .因此,您的问题可能已经以某种方式被注意到,但粗略的搜索并没有产生任何可能与您的问题相关的内容。
    由于这似乎是一个合法的错误,并且由于您对代码和正在发生的事情有更深入的了解,我建议您访问他们的 Issues页面并提交新问题。

    关于c# - 当线程已经终止(.NET 5/Core)时,Thread.Join 方法并不总是返回相同的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66315427/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com