タスクとTaskLogger

  • 概要

    一定以上複雑、もしくは計算量や待ち時間がある処理を、
    秀丸エディタに組み入れていくためには、
    async, await もしくは Taskクラス等による
    非同期処理が多かれ少なかれ必要となってきます。

    全てを同期処理で行ってしまうと、エディタの入力に「ひっかかり感」のようなものを覚えたり、
    そのモジュールを実行している間、秀丸本体が何も処理出来ない、といった事態にもなってしまいます。

    残りタスクが視覚的にわかるTaskLogger

    そこで残りタスクが視覚的にわかる「TaskLogger」の使い方を習得しておきましょう。

    下記ソース「Microsoft.VisualStudio.Threading」はnugetからダウンロードのこと

    TaskLogger (出典:C# via .NET Framework 4.5)
    using Microsoft.VisualStudio.Threading;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Threading.Tasks;
    
    public static class TaskLogger
    {
        public enum TaskLogLevel { None, Pending }
        public static TaskLogLevel LogLevel { get; set; }
    
        public sealed class TaskLogEntry
        {
            public Task Task { get; internal set; }
            public String Tag { get; internal set; }
            public DateTime LogTime { get; internal set; }
            public String CallerMemberName { get; internal set; }
            public String CallerFilePath { get; internal set; }
            public Int32 CallerLineNumber { get; internal set; }
            public override string ToString()
            {
                return String.Format($"LogTime={LogTime}, Tag={Tag ?? "none"}, Member={CallerMemberName}, File={CallerFilePath}({CallerLineNumber})");
            }
        }
    
        private static readonly ConcurrentDictionary<Task, TaskLogEntry> s_log = new ConcurrentDictionary<Task, TaskLogEntry>();
        public static IEnumerable<TaskLogEntry> GetLogEntries() { return s_log.Values; }
    
        public static Task<TResult> Log<TResult>(this Task<TResult> task, String tag = null,
            [CallerMemberName] String callerMemberName = null,
            [CallerFilePath] String callerFilePath = null,
            [CallerLineNumber] Int32 callerLineNumber = -1)
        {
            return (Task<TResult>)Log((Task)task, tag, callerMemberName, callerFilePath, callerLineNumber);
        }
    
        public static Task Log(this Task task, String tag = null,
            [CallerMemberName] String callerMemberName = null,
            [CallerFilePath] String callerFilePath = null,
            [CallerLineNumber] Int32 callerLineNumber = -1)
        {
            var logEntry = new TaskLogEntry
            {
                Task = task,
                LogTime = DateTime.Now,
                Tag = tag,
                CallerMemberName = callerMemberName,
                CallerFilePath = callerFilePath,
                CallerLineNumber = callerLineNumber
            };
    
            s_log[task] = logEntry;
    
            task.ContinueWith(
                t => { TaskLogEntry entry; s_log.TryRemove(t, out entry); },
                TaskContinuationOptions.ExecuteSynchronously
            );
            return task;
    
        }
    
    }
    
    
    public class MyClass
    {
        public static async Task Go()
        {
            TaskLogger.LogLevel = TaskLogger.TaskLogLevel.Pending;
    
            var tasks = new List<Task>
            {
                Task.Delay(2000).Log("2s op"),
                Task.Delay(5000).Log("5s op"),
                Task.Delay(6000).Log("6s op")
            };
    
            try
            {
                await Task.WhenAll(tasks).WithCancellation(new CancellationTokenSource(3000).Token);
            }
            catch (OperationCanceledException) { }
    
            foreach (var op in TaskLogger.GetLogEntries().OrderBy(tle => tle.LogTime))
            {
                Console.WriteLine(op);
            }
    
        }
    
        static async void Test()
        {
            await Go();
        }
    
        static void Main()
        {
            Test();
            Console.ReadLine();
        }
    }
    

    上記ソースを実行してみれば、実行開始から「2秒後、5秒後、6秒後」に実行されるタスクが生成され、
    実行から3秒後に「タスクロガー」が発動します。