简体   繁体   中英

Why does multithreaded code using CancellationTokenSource.Cancel require less anti-reordering measures

I'm trying to understand why a shared CancellationTokenSource variable is not protected by a lock or memory barriers here.

I know there is a rule of thumb that a read or a write of a shared (state) variable can be reordered with local variables reads and writes if compiler optimizations are allowed.

Here is an example from the CancellationTokenSource documentation .

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        // Define the cancellation token.
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken token = source.Token;

        Random rnd = new Random();
        Object lockObj = new Object();

        List<Task<int[]>> tasks = new List<Task<int[]>>();
        TaskFactory factory = new TaskFactory(token);
        for (int taskCtr = 0; taskCtr <= 10; taskCtr++)
        {
            int iteration = taskCtr + 1;
            tasks.Add(factory.StartNew(() =>
            {
                int value;
                int[] values = new int[10];
                for (int ctr = 1; ctr <= 10; ctr++)
                {
                    lock (lockObj)
                    {
                        value = rnd.Next(0, 101);
                    }
                    if (value == 0)
                    {
                        source.Cancel();
                        Console.WriteLine("Cancelling at task {0}", iteration);
                        break;
                    }
                    values[ctr - 1] = value;
                }
                return values;
            }, token));
        }
        try
        {
            Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), (results) =>
            {
                Console.WriteLine("Calculating overall mean...");
                long sum = 0;
                int n = 0;
                foreach (var t in results)
                {
                    foreach (var r in t.Result)
                    {
                        sum += r;
                        n++;
                    }
                }
                return sum / (double)n;
            }, token);
            Console.WriteLine("The mean is {0}.", fTask.Result);
        }
        catch (AggregateException ae)
        {
            foreach (Exception e in ae.InnerExceptions)
            {
                if (e is TaskCanceledException)
                    Console.WriteLine("Unable to compute mean: {0}",
                                      ((TaskCanceledException)e).Message);
                else
                    Console.WriteLine("Exception: " + e.GetType().Name);
            }
        }
        finally
        {
            source.Dispose();
        }
    }
}

What's the exact reason for this? Does Microsoft imply that such a reordering would be safe and hence requires no protection measures, or no reordering is possible at all?

As it was noted by the author of The Old New Thing in his comment, source.Cancel(); instruction placed in multithreaded code is protected from reordering by means of its internal implementation.

https://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,723 states that CancellationTokenSource relies upon Interlocked class methods.

According to Joe Albahari, all methods on the Interlocked class in C# implicitly generate full fences: http://www.albahari.com/threading/part4.aspx#_Memory_Barriers_and_Volatility

So one can freely place a call to CancellationTokenSource.Cancel method inside a delegate body without an additional lock or memory barrier if they need to protect it while accessed by multiple tasks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM