راهنمای برنامه نویس دات نت برای CancellationToken


گاهی اوقات لغو کردن چیز خوبی است. در بسیاری از پروژه های دات نت خود، انگیزه زیادی برای لغو فرآیندهای داخلی و خارجی داشته ام. مایکروسافت متوجه شد که توسعه دهندگان در حال نزدیک شدن به این مورد رایج در انواع پیاده سازی های پیچیده هستند و تصمیم گرفت که باید راه بهتری وجود داشته باشد. بنابراین، مشترک الگوی ارتباط لغو به عنوان معرفی شد CancellationToken، که با استفاده از ساختارهای ارتباطی چند رشته ای و بین فرآیندی سطح پایین ساخته شده است. به عنوان بخشی از تحقیقات اولیه من در مورد این الگو – و پس از بررسی کد منبع واقعی دات نت برای پیاده سازی مایکروسافت – متوجه شدم که CancellationToken می‌تواند مجموعه بسیار گسترده‌تری از مشکلات را حل کند: اشتراک در حالت‌های اجرای برنامه‌ها، زمان‌بندی عملیات با استفاده از محرک‌های مختلف، و ارتباطات بین فرآیندی عمومی از طریق پرچم‌ها.

Intended CancellationToken Use Case

CancellationToken در NET 4 به عنوان وسیله ای برای ارتقاء و استاندارد کردن راه حل های موجود برای لغو عملیات معرفی شد. چهار رویکرد کلی برای مدیریت لغو وجود دارد که زبان های برنامه نویسی رایج تمایل به پیاده سازی آن ها دارند:

بکش بگو، نه را به عنوان جواب قبول نکن مودبانه بپرسید و رد را بپذیرید پرچم را مؤدبانه تنظیم کنید، اگر خواست اجازه دهید نظرسنجی کند
رویکرد توقف سخت؛ بعداً ناهماهنگی ها را حل کنید به آن بگویید متوقف شود اما بگذارید همه چیز را تمیز کند یک درخواست مستقیم اما ملایم برای توقف از آن بخواهید متوقف شود، اما مجبورش نکنید
خلاصه راهی مطمئن برای فساد و درد به نقاط توقف تمیز اجازه می دهد اما باید متوقف شود به نقاط توقف تمیز اجازه می دهد، اما درخواست لغو ممکن است نادیده گرفته شود لغو از طریق یک پرچم درخواست می شود
نخ ها pthread_kill،
pthread_cancel (ناهمگام)
pthread_cancel (حالت معوق) n/a از طریق یک پرچم
.خالص Thread.Abort n/a Thread.Interrupt از طریق یک پرچم در CancellationToken
جاوا Thread.destroy،
Thread.stop
n/a Thread.interrupt از طریق یک پرچم یا Thread.interrupted
پایتون PyThreadState_SetAsyncExc n/a asyncio.Task.cancel از طریق یک پرچم
راهنمایی غیر قابل قبول؛ از این رویکرد اجتناب کنید قابل قبول است، به خصوص زمانی که زبانی از استثناها پشتیبانی نمی کند یا باز می شود اگر زبان از آن پشتیبانی کند قابل قبول است بهتر است، اما بیشتر یک تلاش گروهی است
خلاصه رویکرد لغو و مثال‌های زبان

CancellationToken در دسته نهایی قرار دارد، جایی که مکالمه لغو مشارکتی است.

پس از معرفی مایکروسافت CancellationToken، جامعه توسعه به سرعت آن را پذیرفت، به ویژه به این دلیل که بسیاری از API های اصلی دات نت برای استفاده از این توکن ها به صورت بومی به روز شدند. به عنوان مثال، با شروع ASP.NET Core 2.0، اقدامات اختیاری را پشتیبانی می کنند CancellationToken پارامتری که ممکن است اگر یک درخواست HTTP بسته شده باشد، سیگنال می دهد و اجازه لغو هر عملیاتی را می دهد و بنابراین از استفاده بی مورد از منابع جلوگیری می کند.

پس از یک فرو رفتن عمیق در پایگاه کد دات نت، مشخص شد که CancellationTokenاستفاده از آن محدود به لغو نیست.

CancellationToken زیر میکروسکوپ

وقتی با دقت بیشتری به CancellationTokenپیاده سازی، می بینیم که فقط یک پرچم ساده است (یعنی ManualResetEvent) و زیرساخت پشتیبانی که امکان نظارت و تغییر آن پرچم را فراهم می کند. CancellationTokenابزار اصلی در نام آن است که نشان می دهد این روش رایج برای لغو عملیات است. امروزه، هر کتابخانه، بسته یا فریمورک دات نت با عملیات ناهمزمان یا طولانی مدت امکان لغو از طریق این توکن ها را فراهم می کند.

CancellationToken ممکن است با تنظیم دستی پرچم آن بر روی “true” یا برنامه ریزی آن برای تغییر به “true” پس از سپری شدن یک بازه زمانی خاص فعال شود. صرف نظر از اینکه چگونه یک CancellationToken راه اندازی می شود، کد مشتری که این نشانه را نظارت می کند ممکن است ارزش پرچم توکن را از طریق یکی از سه روش تعیین کند:

  • با استفاده از a WaitHandle
  • نظرسنجی CancellationTokenپرچم
  • اطلاع رسانی کد مشتری هنگام به روز رسانی وضعیت پرچم از طریق یک اشتراک برنامه ای

پس از تحقیقات بیشتر در پایگاه کد دات نت، مشخص شد که تیم دات نت یافته است CancellationTokens در سناریوهای دیگر که به لغو مرتبط نیستند مفید است. بیایید برخی از این موارد استفاده پیشرفته و بی‌نام تجاری را بررسی کنیم، که به توسعه‌دهندگان سی شارپ با هماهنگی چند رشته‌ای و بین فرآیندی برای ساده‌سازی موقعیت‌های پیچیده قدرت می‌دهد.

CancellationTokens برای رویدادهای پیشرفته

هنگام نوشتن برنامه های ASP.NET Core، گاهی اوقات نیاز داریم که بدانیم برنامه ما چه زمانی شروع شده است یا باید کد خود را به فرآیند خاموش کردن هاست تزریق کنیم. در این موارد، ما از IHostApplicationLifetime رابط (قبلا IApplicationLifetime). این رابط (از مخزن NET Core) از CancellationToken برای برقراری ارتباط با سه رویداد مهم: ApplicationStarted، ApplicationStopping، و ApplicationStopped:

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Allows consumers to be notified of application lifetime events. 
    /// This interface is not intended to be user-replaceable.
    /// </summary>
    public interface IHostApplicationLifetime
    {
        /// <summary>
        /// Triggered when the application host has fully started.
        /// </summary>
        CancellationToken ApplicationStarted { get; }

        /// <summary>
        /// Triggered when the application host is starting a graceful shutdown.
        /// Shutdown will block until all callbacks registered on 
        /// this token have completed.
        /// </summary>
        CancellationToken ApplicationStopping { get; }

        /// <summary>
        /// Triggered when the application host has completed a graceful shutdown.
        /// The application will not exit until all callbacks registered on 
        /// this token have completed.
        /// </summary>
        CancellationToken ApplicationStopped { get; }

        /// <summary>
        /// Requests termination of the current application.
        /// </summary>
        void StopApplication();
    }
}

در نگاه اول، ممکن است به نظر برسد CancellationTokens به اینجا تعلق ندارند، به خصوص که از آنها به عنوان رویداد استفاده می شود. با این حال، بررسی بیشتر نشان می دهد که این نشانه ها مناسب هستند:

  • آنها منعطف هستند و راه های متعددی را در اختیار مشتری رابط قرار می دهند تا به این رویدادها گوش دهد.
  • آنها خارج از جعبه ایمن با نخ هستند.
  • آنها را می توان از منابع مختلف با ترکیب ایجاد کرد CancellationTokenس

با اينكه CancellationTokens برای هر رویدادی مناسب نیستند، آنها برای رویدادهایی که فقط یک بار اتفاق می‌افتند، مانند شروع یا توقف برنامه، ایده‌آل هستند.

CancellationToken برای Timeout

به طور پیش فرض، ASP.NET زمان بسیار کمی را برای خاموش کردن در اختیار ما قرار می دهد. در مواردی که ما کمی زمان بیشتری می خواهیم، ​​با استفاده از داخلی HostOptions کلاس به ما این امکان را می دهد که این مقدار وقفه را تغییر دهیم. در زیر، این مقدار وقفه در یک بسته بندی شده است CancellationToken و وارد فرآیندهای فرعی اساسی می شود.

IHostedService‘s StopAsync متد یک مثال عالی از این استفاده است:

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Defines methods for objects that are managed by the host.
    /// </summary>
    public interface IHostedService
    {
        /// <summary>
        /// Triggered when the application host is ready to start the service.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the start
        ///     process has been aborted.</param>
        Task StartAsync(CancellationToken cancellationToken);

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown 
        ///     process should no longer be graceful.</param>
        Task StopAsync(CancellationToken cancellationToken);
    }
}

همانطور که در IHostedService تعریف رابط، StopAsync روش یکی را می گیرد CancellationToken پارامتر. کامنت مرتبط با آن پارامتر به وضوح هدف اولیه مایکروسافت را نشان می دهد CancellationToken به‌عنوان یک مکانیسم زمان‌بندی به جای یک فرآیند لغو بود.

به نظر من، اگر این رابط قبلا وجود داشت CancellationTokenوجود، این می توانست یک TimeSpan پارامتر – برای نشان دادن مدت زمانی که عملیات توقف مجاز به پردازش است. در تجربه من، سناریوهای تایم اوت تقریباً همیشه می توانند به a تبدیل شوند CancellationToken با ابزار اضافی عالی

در حال حاضر، بیایید فراموش کنیم که می دانیم چگونه StopAsync روش طراحی شده است و در عوض به این فکر کنید که چگونه قرارداد این روش را طراحی کنیم. ابتدا بیایید الزامات را تعریف کنیم:

  • را StopAsync روش باید سعی کند سرویس را متوقف کند.
  • را StopAsync روش باید حالت توقف برازنده داشته باشد.
  • صرف نظر از اینکه آیا یک حالت توقف برازنده به دست می‌آید، یک سرویس میزبان باید حداکثر زمان توقف را داشته باشد، همانطور که توسط پارامتر مهلت زمانی ما تعریف شده است.

با داشتن یک StopAsync روش در هر شکل، ما نیاز اول را برآورده می کنیم. الزامات باقی مانده مشکل هستند. CancellationToken این الزامات را دقیقاً با استفاده از یک ابزار ارتباطی مبتنی بر پرچم دات نت استاندارد برای تقویت مکالمه برآورده می کند.

CancellationToken به عنوان مکانیزم اطلاع رسانی

بزرگترین راز پشت CancellationToken این است که فقط یک پرچم است. بیایید نشان دهیم که چگونه CancellationToken می توان برای شروع فرآیندها به جای توقف آنها استفاده کرد.

موارد زیر را در نظر بگیرید:

  1. ایجاد یک RandomWorker کلاس
  2. RandomWorker باید یک DoWorkAsync روشی که چند کار تصادفی را اجرا می کند.
  3. را DoWorkAsync متد باید به تماس گیرنده اجازه دهد که مشخص کند کار باید چه زمانی شروع شود.
public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public async Task DoWorkAsync()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

کلاس بالا دو شرط اول را برآورده می کند و سومی را برای ما باقی می گذارد. چندین رابط جایگزین وجود دارد که می‌توانیم از آنها برای فعال کردن کارگرمان استفاده کنیم، مانند یک بازه زمانی یا یک پرچم ساده:

# With a time span
Task DoWorkAsync(TimeSpan startAfter);

# Or a simple flag
bool ShouldStart { get; set; }
Task DoWorkAsync();

این دو رویکرد خوب هستند، اما هیچ چیز به زیبایی استفاده از a نیست CancellationToken:

public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public async Task DoWorkAsync(CancellationToken startToken)
    {
        startToken.WaitHandle.WaitOne();

        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

این نمونه کد مشتری قدرت این طراحی را نشان می دهد:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CancelToStart
{
    public class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource startCts = new CancellationTokenSource();

            startCts.CancelAfter(TimeSpan.FromSeconds(10));

            var tasks = Enumerable.Range(0, 10)
                .Select(i => new RandomWorker(i))
                .Select(worker => worker.DoWorkAsync(startCts.Token))
                .ToArray();

            Task.WaitAll(tasks, CancellationToken.None);
        }
    }
}

را CancellationTokenSource ما را ایجاد خواهد کرد CancellationToken پشت صحنه و هماهنگی راه اندازی تمام فرآیندهای مرتبط. در این مورد، فرآیند مرتبط ما است RandomWorker، که منتظر شروع است. این رویکرد به ما اجازه می‌دهد تا از ایمنی نخ پخته شده در حالت پیش‌فرض استفاده کنیم CancellationToken پیاده سازی.

این مثال ها نشان می دهد که چگونه CancellationToken جعبه ابزاری از راه حل ها را ارائه می دهد که خارج از مورد استفاده مورد نظر آن مفید هستند. این ابزارها می توانند در بسیاری از سناریوها که شامل ارتباطات مبتنی بر پرچم بین پردازشی هستند، مفید باشند. چه با مهلت زمانی، اعلان‌ها یا رویدادهای یک‌باره مواجه باشیم، می‌توانیم به این پیاده‌سازی زیبا و آزمایش‌شده توسط مایکروسافت بازگردیم.

از بالا به پایین، کلمات
Toptal به عنوان یک شریک طلایی مایکروسافت، شبکه نخبگان شما از کارشناسان مایکروسافت است. تیم هایی با عملکرد بالا با کارشناسانی که نیاز دارید بسازید – در هر کجا و دقیقاً زمانی که به آنها نیاز دارید!

ادامه مطلب در وبلاگ مهندسی تاپتال:



منبع

Matthew Newman

Matthew Newman Matthew has over 15 years of experience in database management and software development, with a strong focus on full-stack web applications. He specializes in Django and Vue.js with expertise deploying to both server and serverless environments on AWS. He also works with relational databases and large datasets
[ Back To Top ]