در برنامههای Blazor Server، تنها از یک نخ رابط کاربری واحد ( single UI thread ) استفاده نمیشود؛ بلکه هر نخی که در دسترس باشد، میتواند در موقع رندر، استفاده شود. علاوه بر این اگر از عملیات نامتقارن استفاده شود، زمانیکه به کلمهی کلیدی await میرسیم، آنگاه نخ اختصاص داده شدهی برای ادامه پردازش متد، ممکن است لزوما همان چیزی نباشد که آن را شروع کرده است. برای نشان دادن این موضوع مثالی را در پیش میگیریم.
کامپوننتی را با نام SynchronousInitComponent با کد زیر درنظر میگیریم. همانطور که از اسم آن مشخص است این کامپوننت به صورت متقارن یا همزمان پیادهسازی شده است:
<p>Sync rendered by thread @IdOfRenderingThread</p>
@code
{
int IdOfRenderingThread;
protected override void OnInitialized()
{
base.OnInitialized();
IdOfRenderingThread =
System.Threading.Thread.CurrentThread.ManagedThreadId;
}
}
در حقیقت در متد OnInitialized آن، مقدار نخ جاری را توسط Thread.ManagedThreadId به دست میآوریم. بنابراین شماره نخ جاری پس از رندر شدن کامپوننت، در صفحه نمایش داده میشود.
حال در کامپوننت دیگری برای مثال کامپوننت index، کامپوننت همزمان فوق را به شکل زیر فراخوانی میکنیم:
@page "/"
<h1>Components with synchronous OnInitialized()</h1>
@for (int i = 0; i < 5; i++)
{
<SynchronousInitComponent />
}
با این نتیجه:
Components with synchronous OnInitialized()
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
همانطور که ملاحظه مینمایید شناسه نخ یکسانی برای هر فراخوانی کامپوننت نشان داده میشود. بدیهی است در صورتیکه شما همین کد را اجرا کنید، ممکن است شماره نخ برنامه شما با کد من یکی نباشد؛ اما نتیجه یکی است. یعنی در تمامی موارد، یک عدد مشاهده میشود.
حال همین آزمایش را با متدهای نامتقارن یا ناهمزمان انجام میدهیم. کامپوننت AsynchronousInitComponent را با کد زیر درنظر بگیرید:
<p>Async rendered by thread @IdOfRenderingThread</p>
@code
{
int IdOfRenderingThread;
protected override async Task OnInitializedAsync()
{
// Runs synchronously as there is no code in base.OnInitialized(),
// so the same thread is used
await base.OnInitializedAsync().ConfigureAwait(false);
IdOfRenderingThread =
System.Threading.Thread.CurrentThread.ManagedThreadId;
// Awaiting will schedule a job for later, and we will be assigned
// whichever worker thread is next available
await Task.Delay(1000).ConfigureAwait(false);
IdOfRenderingThread =
System.Threading.Thread.CurrentThread.ManagedThreadId;
}
}
این کامپوننت هم دقیقا شبیه کامپوننت قبلی است؛ با این تفاوت که IdOfRenderingThread، مجددا بعد از یک تاخیر یک ثانیهای مقداردهی شدهاست. این مقداردهی سبب رندر مجدد کامپوننت با تاخیر یک ثانیه میشود. حال در کامپوننت دیگری، کامپوننت غیرمتقارن فوق را به شکل زیر فراخوانی میکنیم:
@page "/async-init"
<h1>Components with asynchronous OnInitializedAsync()</h1>
@for (int i = 0; i < 5; i++)
{
<AsynchronousInitComponent/>
}
با اجرا کردن برنامه، دقیقا نتایج، شبیه نتایج نتایج کامپوننت متقارن میباشد:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
اما تنها بعد از یک ثانیه (await Task.Delay(1000)) ، متدهای OnInitializedAsync کامپوننتها، به پایان میرسند و مقدار IdOfRenderingThread را پیش از به پایان رسیدن رندر صفحه، به روز رسانی مینمایند. اینبار، دیگر مقادیر نخها متفاوت خواهند بود:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 7
Async rendered by thread 18
Async rendered by thread 10
Async rendered by thread 13
Async rendered by thread 11
با توجه به مطالب مطرح شده به این نتیجه میرسیم که این موضوع میتواند هنگام استفاده از یک وابستگی غیر ایمن ( non-thread-safe dependency ) مانند DBContext در چندین کامپوننت باعث بروز مشکل شود. نمونهای از نحوهی رویارویی با مشکلات احتمالی آن،
در اینجا و
اینجا بررسی شدهاست.