Ny samlet måde at håndtere langtidskørende processer på i .NET

Siden .NET 4 har der været en ny forenet måde at håndtere standsningen af processer, som skal køre i lang tid. Førhen (man ser det faktisk stadig i utallige af artikler og tutorials rundt omkring), har foreslaget altid været en while(true), med en sleep, eller en Console.ReadKey, dog er dette ikke tilfældet mere, hvilket man kan læse meget mere om her.

For .NET 4 var rådet altid at man brugte en Console.ReadKey for at holde processen fra at terminere

using System;
using System.IO;

namespace Programming
{
    public class Program
    {
        //...
        //kode som skal køre lang tid
        //...

        Console.WriteLine("press key to stop");
        Console.Readkey();
    }
}

En notice til læseren: Jeg startede først med at bruge .NET lige efter .NET 4 udkom, så teoretisk set ved jeg faktisk ikke om der vitterlig har været en hel anden måde at gøre det på, dog studser jeg over, hver gang jeg enten læser

at man altid nævner while(true), med en sleep, eller en Console.ReadKey som løsningen på, ikke at få processen til at stoppe, og jeg er egentlig ligeglad med om det "bare" er vis-og-smid-væk kode, eller, at selve det at stoppe termineringen ikke erhoved fokus, for det som afsenderen vil vise. Man kan lige så godt vende læseren (dig og mig), til at gøre det korrekte, og det er på grænsen til det pinlige, at samtlige afsendere endnu ikke ved hvordan man gør (for det antager jeg de ikke ved). Den korrekte måde, hvorved man undgår en process i at terminere, er at lytte på der bliver sendt en Cancel til processen, og derefter terminere korrekt:


using System;

namespace Programming
{
    class Program
    {
        private static System.Threading.Timer _timer;

        static void Main(string[] args)
        {
            var cts = new System.Threading.CancellationTokenSource();
            _timer = new System.Threading.Timer((arg) =>
            {
                Console.WriteLine($"Tick {DateTime.Now}");

            }, null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(10));

            System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
            Console.CancelKeyPress += (sender, cpe) => cts.Cancel();
            WhenCancelled(cts.Token).Wait();
            Console.WriteLine("Console terminating");
        }

        public static System.Threading.Tasks.Task WhenCancelled(System.Threading.CancellationToken cancellationToken)
        {
            var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
            cancellationToken.Register(s => ((System.Threading.Tasks.TaskCompletionSource<bool>)s).SetResult(true), tcs);
            return tcs.Task;
        }
    }
}

Her lytter jeg efter om der

Hvis der gør, fortæl System.Threading.CancellationTokenSource at den skal stoppe. Dette er den korrekte måde at gøre det jvf Microsofts dokumentation, som jeg linkede til før

Starting with the .NET Framework 4, the .NET Framework uses a unified model for cooperative cancellation of asynchronous or long-running synchronous operations that involves two objects:

A CancellationTokenSource object, which provides a cancellation token through its Token property and sends a cancellation message by calling its Cancel or CancelAfter method.

A CancellationToken object, which indicates whether cancellation is requested.

Fra Remarks sektionen på følgende link https://msdn.microsoft.com/en-us/library/system.threading.cancellationtokensource%28v=vs.110%29.aspx.

Den opmærksomme læser vil se at jeg har brugt en timer funktion, OG IKKE EN while(true)-og-en-sleep for at køre noget kode igen. Smart, og let at læse. Læg mærke til at jeg gemmer timeren i en private property, så timeren ikke bliver samlet op af GC'eren, hvis timeren er sat til at have en period (sidste variabel i constructoren), som er længere end 10 eller 20 sekunder. Jeg har været ude for det på en period som var sat til to minutter.

Skrevet af Martin Slot den 1/29/2018 2:19:00 PM