گاهی اوقات لغو کردن چیز خوبی است. در بسیاری از پروژه های دات نت خود، انگیزه زیادی برای لغو فرآیندهای داخلی و خارجی داشته ام. مایکروسافت متوجه شد که توسعه دهندگان در حال نزدیک شدن به این مورد رایج در انواع پیاده سازی های پیچیده هستند و تصمیم گرفت که باید راه بهتری وجود داشته باشد. بنابراین، مشترک الگوی ارتباط لغو به عنوان معرفی شد 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();
}
}
در نگاه اول، ممکن است به نظر برسد CancellationToken
s به اینجا تعلق ندارند، به خصوص که از آنها به عنوان رویداد استفاده می شود. با این حال، بررسی بیشتر نشان می دهد که این نشانه ها مناسب هستند:
- آنها منعطف هستند و راه های متعددی را در اختیار مشتری رابط قرار می دهند تا به این رویدادها گوش دهد.
- آنها خارج از جعبه ایمن با نخ هستند.
- آنها را می توان از منابع مختلف با ترکیب ایجاد کرد
CancellationToken
س
با اينكه CancellationToken
s برای هر رویدادی مناسب نیستند، آنها برای رویدادهایی که فقط یک بار اتفاق میافتند، مانند شروع یا توقف برنامه، ایدهآل هستند.
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
می توان برای شروع فرآیندها به جای توقف آنها استفاده کرد.
موارد زیر را در نظر بگیرید:
- ایجاد یک
RandomWorker
کلاس -
RandomWorker
باید یکDoWorkAsync
روشی که چند کار تصادفی را اجرا می کند. - را
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
جعبه ابزاری از راه حل ها را ارائه می دهد که خارج از مورد استفاده مورد نظر آن مفید هستند. این ابزارها می توانند در بسیاری از سناریوها که شامل ارتباطات مبتنی بر پرچم بین پردازشی هستند، مفید باشند. چه با مهلت زمانی، اعلانها یا رویدادهای یکباره مواجه باشیم، میتوانیم به این پیادهسازی زیبا و آزمایششده توسط مایکروسافت بازگردیم.