اشتراکها
مایکروسافت ابزار اتوماسیون آنالیز کرش و خطرات امنیتی حاصل از آنرا که پیشتر در تیمهای داخلی خودش مورد استفاده قرار میگرفت، سورس باز کرد.
برای نمونه از این ابزار در طی سالهای 2005 تا 2006 جهت بررسی کدهای ویندوز ویستا بهره برداری شده و توسط آن بیش از 300 باگ امنیتی پیش از سوء استفاده از آنها کشف و برطرف گردیده است. این ابزار اطلاعات حاصل از یک کرش را بررسی کرده و باگهای امنیتی ممکن آنرا گوشزد میکند.
صفحهی اصلی پروژه در CodePlex
exploitable Crash Analyzer
و دمویی در مورد فلسفهی وجودی و کاربردهای این ابزار
دریافت
یکی از مواردی که با Angular CLI 6.0 به شدت ساده شدهاست، ایجاد پروژههای «کتابخانه» Angular است. برای مثال شاید در حین استفادهی از بعضی از کتابخانهی ثالث تهیه شدهی برای Angular با خطای ذیل مواجه شده باشید:
این خطا زمانی رخ میدهد که تهیه کنندهی کتابخانه، فرمت بستههای Angular را رعایت نکرده باشد و ... رعایت کردن آن نیز کار بسیار مشکلی است. نگارش 6 در پشت صحنه، پروژهی موفق ng-packagr را به مجموعهی CLI اضافه کردهاست و از این پس توسط خود CLI میتوان کتابخانههای استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانههای npm حاصل را نیز به همراه دارد. مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار با بستههای فعلی npm مخصوص Angular وجود دارند؛ مانند خطایی که عنوان شد. برای مثال بدون استفادهی از این ابزار، نیاز است مستندات چند صفحهای ساخت کتابخانههای Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0 به صورت خودکار ایجاد و مدیریت میشود.
مراحل ایجاد یک پروژهی «کتابخانه» توسط Angular CLI 6.0
مرحلهی اول ایجاد یک پروژهی کتابخانه، مانند قبل، توسط دستور ng new و ایجاد یک پروژهی دلخواه جدید است:
به همراه Angular CLI 6.0، فرمت تنظیمات آن نیز تغییر کردهاست و مفهوم workspace به آن اضافه شدهاست که در آن میتوان چندین پروژه را تعریف کرد.
پس از ایجاد پروژهی my-lib-test توسط دستور فوق و وارد شدن به پوشهی اصلی آن توسط خط فرمان، میتوان با اجرای دستور زیر، پروژههای دیگری را به پروژهی جاری افزود:
اما اگر در اینجا بجای ذکر application، از نام library استفاده کنیم، یک کتابخانه را بجای یک برنامه، به workspace جاری اضافه میکند:
پس از اجرای این دستور اگر به فایل angular.json دقت کنیم، این پروژه در ذیل projects اضافه شدهاست:
همچنین یک پوشهی جدید به نام projects نیز ایجاد شده و پروژهی my-lib داخل آن قرار گرفتهاست.
فایل جدید public_api.ts
پس از ایجاد کتابخانهی جدید «my-lib»، فایل جدیدی به نام projects\my-lib\src\public_api.ts نیز به آن اضافه شدهاست:
با این محتوا:
هر خروجی که در اینجا ذکر شود توسط استفاده کنندگان از این کتابخانه قابل دسترسی خواهد بود. برای مثال دستور «ng generate library my-lib» مطابق تصویر فوق، یک سرویس جدید را به نام my-lib.service، یک کامپوننت جدید را به نام my-lib.component و یک ماژول جدید را به نام my-lib.module به صورت پیشفرض ایجاد کرده و درون پوشهی lib قرار دادهاست. اکنون آنها را توسط فایل public_api.ts، به نحوی که مشاهده میکنید در معرض دید استفاده کنندگان قرار میدهد.
برای مثال اگر فایل جدید projects\my-lib\src\lib\my-lib.models.ts را به این کتابخانه اضافه کنیم که شامل تعدادی مدل و اینترفیس قابل دسترسی توسط استفاده کنندگان باشد، باید یک سطر زیر را به انتهای فایل public_api.ts اضافه کنیم:
این پروژهی کتابخانه حتی به همراه فایلهای package.json, tsconfig.json, tslint.json مخصوص به خود نیز میباشد تا بتوان آنها را صرفا جهت این پروژه سفارشی سازی کرد.
ساختار my-lib.service پیشفرض یک پروژهی کتابخانه
اگر به فایل projects\my-lib\src\lib\my-lib.service.ts دقت کنیم:
تمام قسمتهای آن مانند قبل است، منهای 'providedIn: 'root آن. این مورد تنظیم جدیدی است که در پروژههای Angular 6 قابل استفادهاست. هدف از آن، ارائهی یک سرویس، بدون نیاز به ثبت صریح آن در قسمت providers یک NgModule است.
شاید بپرسید چرا؟ هدف اصلی از آن، بهبود فرآیند tree-shaking یا حذف کدهای مرده و استفاده نشدهاست. ممکن است سرویسی را تعریف کنید، اما در برنامه استفاده نشود. این حالت خصوصا در پروژههای کتابخانههای ثالث ممکن است زیاد رخ دهد. به همین جهت با ارائهی این قابلیت، امکان حذف سادهتر سرویسهایی که در برنامه استفاده نشدهاند از خروجی نهایی کامپایل شده، وجود خواهد داشت.
چگونه به پروژهی کتابخانهی جدید، یک کامپوننت جدید را اضافه کنیم؟
تمام دستورات Angular CLI، در اینجا نیز کار میکنند. تنها تفاوت آنها، ذکر صریح نام پروژهی مورد استفاده است:
دستور فوق کامپوننت جدید show-data را به پروژهی my-lib اضافه خواهد کرد؛ به همراه به روز رسانی خودکار فایل projects/my-lib/src/lib/my-lib.module.ts این پروژه، جهت ثبت کامپوننت اضافه شده.
البته در اینجا باید فایل my-lib.module.ts را اندکی ویرایش کرد و ShowDataComponent را به قسمت exports نیز افزود:
به صورت پیشفرض، کامپوننت جدید را در قسمت declarations معرفی میکند. یک چنین کامپوننتی فقط داخل همان lib قابل استفادهاست. اگر قرار است خارج از این lib نیز به آن دسترسی داشته باشیم، باید آنرا در قسمت exports نیز قید کنیم.
همچنین قسمت imports آن نیز به صورت پیشفرض خالی است. اگر نیاز است با ngIf کار کنید، باید CommonModule را در اینجا قید کنید و اگر نیاز است تبادلات HTTP وجود داشته باشد، ذکر HttpClientModule نیز ضروری است.
مرحلهی ساخت پروژه
پیش از استفادهی از این پروژهی کتابخانه، باید آنرا build کرد:
در اینجا نیز دستور ng build مانند قبل است، با این تفاوت که نام پروژهی کتابخانه نیز در اینجا ذکر شدهاست.
پس از اجرای این دستور، خروجی ذیل مشاهده میشود:
همانطور که ملاحظه میکنید، پس از طی مراحل خاص تولید یک کتابخانه، خروجی نهایی آنرا در پوشهی dist\my-lib قرار دادهاست.
استفادهی از کتابخانهی تولید شده
پس از پایان موفقیت آمیز مرحلهی Build، اکنون نوبت به استفادهی از این کتابخانه است. استفادهی از آن نیز همانند تمام کتابخانهها و وابستگیهای ثالثی است که تا پیش از این از آنها استفاده کردهایم. برای مثال ماژول آنرا در قسمت imports مربوط به NgModule کلاس AppModule معرفی میکنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و MyLibModule را به نحو ذیل اضافه میکنیم:
نکتهی مهمی که در اینجا باید به آن دقت داشت این است که هرچند در این پروژه، MyLibModule داخل پوشهی projects\my-lib\src\lib قرار دارد، اما نباید مسیر نسبی آنرا در اینجا ذکر کرد و باید صرفا نام پوشهی my-lib واقع در پوشهی node_modules را در اینجا در حین مسیر دهی import آن معرفی کرد (همانند تمام وابستگیهای ثالث دیگر).
اما سؤال اینجا است که آیا این پوشه پس از build، داخل پوشهی node_modules نیز کپی شدهاست؟ پاسخ آن خیر است و برای مدیریت خودکار آن، به صورت زیر عمل شدهاست:
اگر به فایل tsconfig.json اصلی و واقع در ریشهی workspace دقت کنید، پس از اجرای دستور «ng generate library my-lib»، قسمت paths آن نیز به صورت خودکار ویرایش شدهاست:
معنای آن این است که هرگاه import ایی در برنامه به my-lib اشاره کند، کامپایلر TypeScript میداند که باید آنرا از پوشهی dist/my-lib دریافت و پردازش کند. به همین جهت در اینجا دیگر نیازی به کپی دستی این پوشه، به پوشهی node_modules وجود ندارد.
به این ترتیب مسیر این import، چه در این پروژهی محلی و چه برای کسانیکه پوشهی dist/my-lib را به صورت یک بستهی npm جدید دریافت کردهاند، یکی خواهد بود.
در ادامه اگر به فایل app.component.html مراجعه کرده و selector کامپوننت show-data را به آن اضافه کنیم:
میتوان محتویات این کامپوننت دریافت شدهی از کتابخانه را مشاهده کرد.
توزیع کتابخانهی ایجاد شده برای عموم
برای اینکه این کتابخانهی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آنرا publish کرد:
سطر اول، کتابخانهی my-lib را در حالت production تواید میکند. سپس به پوشهی فایلهای نهایی تولید شده وارد میشویم و دستور npm publish را صادر میکنیم.
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آنرا دارد. جزئیات بیشتر آن در اینجا.
Please open an issue in the library repository to alert its author and ask them to package the library using the Angular Package Format (https://goo.gl/jB3GVv).
مراحل ایجاد یک پروژهی «کتابخانه» توسط Angular CLI 6.0
مرحلهی اول ایجاد یک پروژهی کتابخانه، مانند قبل، توسط دستور ng new و ایجاد یک پروژهی دلخواه جدید است:
ng new my-lib-test
پس از ایجاد پروژهی my-lib-test توسط دستور فوق و وارد شدن به پوشهی اصلی آن توسط خط فرمان، میتوان با اجرای دستور زیر، پروژههای دیگری را به پروژهی جاری افزود:
ng generate application my-app-name
ng generate library my-lib
همچنین یک پوشهی جدید به نام projects نیز ایجاد شده و پروژهی my-lib داخل آن قرار گرفتهاست.
فایل جدید public_api.ts
پس از ایجاد کتابخانهی جدید «my-lib»، فایل جدیدی به نام projects\my-lib\src\public_api.ts نیز به آن اضافه شدهاست:
با این محتوا:
/* * Public API Surface of my-lib */ export * from './lib/my-lib.service'; export * from './lib/my-lib.component'; export * from './lib/my-lib.module';
برای مثال اگر فایل جدید projects\my-lib\src\lib\my-lib.models.ts را به این کتابخانه اضافه کنیم که شامل تعدادی مدل و اینترفیس قابل دسترسی توسط استفاده کنندگان باشد، باید یک سطر زیر را به انتهای فایل public_api.ts اضافه کنیم:
export * from './lib/my-lib.models';
این پروژهی کتابخانه حتی به همراه فایلهای package.json, tsconfig.json, tslint.json مخصوص به خود نیز میباشد تا بتوان آنها را صرفا جهت این پروژه سفارشی سازی کرد.
ساختار my-lib.service پیشفرض یک پروژهی کتابخانه
اگر به فایل projects\my-lib\src\lib\my-lib.service.ts دقت کنیم:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class MyLibService { constructor() { } }
شاید بپرسید چرا؟ هدف اصلی از آن، بهبود فرآیند tree-shaking یا حذف کدهای مرده و استفاده نشدهاست. ممکن است سرویسی را تعریف کنید، اما در برنامه استفاده نشود. این حالت خصوصا در پروژههای کتابخانههای ثالث ممکن است زیاد رخ دهد. به همین جهت با ارائهی این قابلیت، امکان حذف سادهتر سرویسهایی که در برنامه استفاده نشدهاند از خروجی نهایی کامپایل شده، وجود خواهد داشت.
چگونه به پروژهی کتابخانهی جدید، یک کامپوننت جدید را اضافه کنیم؟
تمام دستورات Angular CLI، در اینجا نیز کار میکنند. تنها تفاوت آنها، ذکر صریح نام پروژهی مورد استفاده است:
ng generate component show-data --project=my-lib
البته در اینجا باید فایل my-lib.module.ts را اندکی ویرایش کرد و ShowDataComponent را به قسمت exports نیز افزود:
@NgModule({ imports: [ CommonModule, HttpClientModule ], declarations: [MyLibComponent, ShowDataComponent], exports: [MyLibComponent, ShowDataComponent] }) export class MyLibModule { }
همچنین قسمت imports آن نیز به صورت پیشفرض خالی است. اگر نیاز است با ngIf کار کنید، باید CommonModule را در اینجا قید کنید و اگر نیاز است تبادلات HTTP وجود داشته باشد، ذکر HttpClientModule نیز ضروری است.
مرحلهی ساخت پروژه
پیش از استفادهی از این پروژهی کتابخانه، باید آنرا build کرد:
ng build my-lib
پس از اجرای این دستور، خروجی ذیل مشاهده میشود:
Building Angular Package Building entry point 'my-lib' Rendering Stylesheets Rendering Templates Compiling TypeScript sources through ngc Downleveling ESM2015 sources through tsc Bundling to FESM2015 Bundling to FESM5 Bundling to UMD Minifying UMD bundle Remap source maps Relocating source maps Copying declaration files Writing package metadata Removing scripts section in package.json as it's considered a potential security vulnerability. Built my-lib Built Angular Package! - from: D:\my-lib-test\projects\my-lib - to: D:\my-lib-test\dist\my-lib
استفادهی از کتابخانهی تولید شده
پس از پایان موفقیت آمیز مرحلهی Build، اکنون نوبت به استفادهی از این کتابخانه است. استفادهی از آن نیز همانند تمام کتابخانهها و وابستگیهای ثالثی است که تا پیش از این از آنها استفاده کردهایم. برای مثال ماژول آنرا در قسمت imports مربوط به NgModule کلاس AppModule معرفی میکنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و MyLibModule را به نحو ذیل اضافه میکنیم:
import { MyLibModule } from "my-lib"; @NgModule({ imports: [ BrowserModule, MyLibModule ] }) export class AppModule { }
اما سؤال اینجا است که آیا این پوشه پس از build، داخل پوشهی node_modules نیز کپی شدهاست؟ پاسخ آن خیر است و برای مدیریت خودکار آن، به صورت زیر عمل شدهاست:
اگر به فایل tsconfig.json اصلی و واقع در ریشهی workspace دقت کنید، پس از اجرای دستور «ng generate library my-lib»، قسمت paths آن نیز به صورت خودکار ویرایش شدهاست:
{ "compilerOptions": { "paths": { "my-lib": [ "dist/my-lib" ] } } }
برای نمونه اگر شارهگر ماوس را بر روی my-lib قرار دهید، به درستی مسیر خوانده شدن آن، تشخیص داده میشود.
به این ترتیب مسیر این import، چه در این پروژهی محلی و چه برای کسانیکه پوشهی dist/my-lib را به صورت یک بستهی npm جدید دریافت کردهاند، یکی خواهد بود.
در ادامه اگر به فایل app.component.html مراجعه کرده و selector کامپوننت show-data را به آن اضافه کنیم:
<lib-show-data></lib-show-data>
توزیع کتابخانهی ایجاد شده برای عموم
برای اینکه این کتابخانهی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آنرا publish کرد:
ng build my-lib --prod cd dist/my-lib npm publish
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آنرا دارد. جزئیات بیشتر آن در اینجا.
اشتراکها
ساخت بازی با WinForms
در زبان #C، زمانیکه از کلاسی ارثبری میشود، امکان بازنویسی متدهای کلاس پایه، در صورت معرفی آنها به صورت virtual و یا abstract، وجود دارد؛ اما در این بازنویسیها، تغییر نوع خروجی این متدها مجاز نیست. این محدودیت در C# 9.0 با معرفی Covariant returns برداشته شدهاست؛ با یک شرط: نوع جدید این خروجی، باید covariant نوع اصلی متدی باشد که از کلاس پایهی آن ارثبری شدهاست.
وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0
برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
در اینجا یک کلاس abstract و پایهی Product وجود دارد که میتوان متد abstract سفارش دهی آنرا در کلاسهای مشتق شدهی از آن، مانند Book، بازنویسی کرد.
همانطور که مشاهده میکنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده میتوان درنظر گرفت، همانی است که در کلاس پایهی Product تعریف شدهاست و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفادهی از این کلاسها، تبدیل خروجی متد Order، به BookOrder ضروری است:
امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0
در C# 9.0 با مجاز اعلام شدن خروجیهای covariant، میتوان تغییرات زیر را به کدهای فوق اعمال کرد:
چون کلاس BookOrder از ProductOrder تعریف شدهی در کلاس پایه مشتق شدهاست (مفهوم covariant بودن خروجی متد)، میتوان در C# 9.0 آنرا به عنوان خروجی متد Order بازنویسی شدهی در کلاس Book، تنظیم کرد.
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائهی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازیها منتفی میکند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را میتوان به صورت زیر خلاصه کرد:
وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0
برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
public abstract class Product { public string Name { get; set; } public abstract ProductOrder Order(int quantity); } public class Book : Product { public string ISBN { get; set; } public override ProductOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; } public class ProductOrder { public int Quantity { get; set; } } public class BookOrder : ProductOrder { public Book Product { get; set; } }
همانطور که مشاهده میکنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده میتوان درنظر گرفت، همانی است که در کلاس پایهی Product تعریف شدهاست و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفادهی از این کلاسها، تبدیل خروجی متد Order، به BookOrder ضروری است:
var book = new Book { Name = "My book", ISBN = "11-1-12-22-0" }; BookOrder orderBook = (BookOrder)book.Order(1);
امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0
در C# 9.0 با مجاز اعلام شدن خروجیهای covariant، میتوان تغییرات زیر را به کدهای فوق اعمال کرد:
public class Book : Product { public string ISBN { get; set; } public override BookOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; }
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائهی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازیها منتفی میکند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را میتوان به صورت زیر خلاصه کرد:
BookOrder orderBook = book.Order(1);
This page describes how to use the Microsoft.CodeAnalysis.Metrics package to perform source code analysis of .NET assemblies from a console application. Visual Studio users can perform source code analysis by clicking the "Analyze" dropdown menu and selecting "Calculate Code Metrics", but I sought to automate this process so I can generate custom code analysis reports from console applications as part of my CI pipeline.
اشتراکها
آموزش Unit Testing در Asp.net Core
ConcurrentDictionary، ساختار دادهای است که امکان افزودن، دریافت و حذف عناصری را به آن به صورت thread-safe میسر میکند. اگر در برنامهای نیاز به کار با یک دیکشنری توسط چندین thread وجود داشته باشد، ConcurrentDictionary راهحل مناسبی برای آن است.
اکثر متدهای این کلاس thread-safe طراحی شدهاند؛ اما با یک استثناء: متد GetOrAdd آن thread-safe نیست:
بررسی نحوهی کار با متد GetOrAdd
این متد یک کلید را دریافت کرده و سپس بررسی میکند که آیا این کلید در مجموعهی جاری وجود دارد یا خیر؟ اگر کلید وجود داشته باشد، مقدار متناظر با آن بازگشت داده میشود و اگر خیر، delegate ایی که به عنوان پارامتر دوم آن معرفی شدهاست، اجرا خواهد شد، سپس مقدار بازگشت داده شدهی توسط آن به مجموعه اضافه شده و در آخر این مقدار به فراخوان بازگشت داده میشود.
در این مثال زمانیکه اولین GetOrAdd فراخوانی میشود، مقدار item 1 بازگشت داده خواهد شد و همچنین این مقدار را در مجموعهی جاری، به کلید key1 انتساب میدهد. در دومین فراخوانی، چون key1 در دیکشنری، دارای مقدار است، همان را بازگشت میدهد و دیگر به value factory ارائه شده مراجعه نخواهد کرد. بنابراین خروجی این مثال به صورت ذیل است:
دسترسی همزمان به متد GetOrAdd امن نیست
ConcurrentDictionary برای اغلب متدهای آن به صورت توکار مباحث قفلگذاری چند ریسمانی را اعمال میکند؛ اما نه برای متد GetOrAdd. زمانیکه valueFactory آن در حال اجرا است، دسترسی همزمان به آن thread-safe نیست و ممکن است بیش از یکبار فراخوانی شود.
یک مثال:
یک نمونه خروجی این مثال میتواند به صورت ذیل باشد:
در اینجا هر چند 10 آیتم در دیکشنری ذخیره شدهاند، اما عملیاتی که در value factory متد GetOrAdd آن صورت گرفته، 13 بار اجرا شدهاست (بجای 10 بار).
علت اینجا است که در این بین، متد GetOrAdd توسط ترد A فراخوانی میشود، اما key را در دیکشنری جاری پیدا نمیکند. به همین جهت شروع به اجرای valueFactory آن خواهد کرد. در همین زمان ترد B نیز به دنبال همین key است. ترد قبلی هنوز به پایان کار خودش نرسیدهاست که مجددا valueFactory متعلق به همین key اجرا خواهد شد. به همین جهت است که در ConcurrentStack اجرا شدهی در valueFactory، بیش از 10 آیتم موجود هستند.
الگویی برای مدیریت دسترسی همزمان امن به متد GetOrAdd
یک روش برای دسترسی همزمان امن به متد GetOrAdd، توسط تیم ASP.NET Core به صورت ذیل ارائه شدهاست:
در اینجا با استفاده از کلاس Lazy، از ایجاد چندین pipeline به ازای یک key مشخص جلوگیری شدهاست.
یک مثال:
با این خروجی:
اینبار، هم dictionary و هم addStack دارای 10 عضو هستند که به معنای تنها اجرای 10 بار value factory است و نه بیشتر.
در این مثال دو تغییر صورت گرفتهاند:
الف) مقادیر ConcurrentDictionary به صورت Lazy معرفی شدهاند.
ب) متد GetOrAdd نیز یک مقدار Lazy را بازگشت میدهد.
زمانیکه از اشیاء Lazy استفاده میشود، خروجیهای بازگشتی از GetOrAdd، توسط این اشیاء Lazy محصور خواهند شد. اما نکتهی مهم اینجا است که هنوز value factory آنها فراخوانی نشدهاست. این فراخوانی تنها زمانی صورت میگیرد که به خاصیت Value یک شیء Lazy دسترسی پیدا کنیم و این دسترسی نیز به صورت thread-safe طراحی شدهاست. یعنی حتی اگر چند ترد new Lazy یک key مشخص را بازگشت دهند، تنها یکبار value factory متد GetOrAdd با دسترسی به خاصیت Value این اشیاء Lazy فراخوانی میشود و مابقی تردها منتظر مانده و تنها مقدار ذخیره شدهی در دیکشنری را دریافت میکنند و سبب اجرای مجدد value factory سنگین و زمانبر آن، نخواهند شد.
بر این مبنا میتوان یک LazyConcurrentDictionary را نیز به صورت ذیل طراحی کرد:
در اینجا ممکن است چندین ترد همزمان متد GetOrAdd را دقیقا با یک کلید مشخص فراخوانی کنند؛ اما تنها چندین شیء Lazy بسیار سبک که هنوز اطلاعات محصور شدهی توسط آنها اجرا نشدهاست، ایجاد خواهند شد. اولین تردی که به خاصیت Value آن دسترسی پیدا کند، سبب اجرای delegate زمانبر و سنگین آن شده و مابقی تردها مجبور به منتظر ماندن جهت بازگشت این نتیجه از دیکشنری خواهند شد (و نه اجرای مجدد delegate).
در مثال فوق، به صورت صریحی پارامتر LazyThreadSafetyMode نیز مقدار دهی شدهاست. هدف از آن اطمینان حاصل کردن از آغاز این شیء Lazy با دسترسی به خاصیت Value آن، تنها توسط یک ترد است.
نمونهی دیگر کار با خاصیت ویژهی Value شیء Lazy را در مطلب «پشتیبانی توکار از ایجاد کلاسهای Singleton از دات نت 4 به بعد» پیشتر در این سایت مطالعه کردهاید.
اکثر متدهای این کلاس thread-safe طراحی شدهاند؛ اما با یک استثناء: متد GetOrAdd آن thread-safe نیست:
TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);
بررسی نحوهی کار با متد GetOrAdd
این متد یک کلید را دریافت کرده و سپس بررسی میکند که آیا این کلید در مجموعهی جاری وجود دارد یا خیر؟ اگر کلید وجود داشته باشد، مقدار متناظر با آن بازگشت داده میشود و اگر خیر، delegate ایی که به عنوان پارامتر دوم آن معرفی شدهاست، اجرا خواهد شد، سپس مقدار بازگشت داده شدهی توسط آن به مجموعه اضافه شده و در آخر این مقدار به فراخوان بازگشت داده میشود.
var dictionary = new ConcurrentDictionary<string, string>(); var value = dictionary.GetOrAdd("key1", x => "item 1"); Console.WriteLine(value); value = dictionary.GetOrAdd("key1", x => "item 2"); Console.WriteLine(value);
item 1 item 1
دسترسی همزمان به متد GetOrAdd امن نیست
ConcurrentDictionary برای اغلب متدهای آن به صورت توکار مباحث قفلگذاری چند ریسمانی را اعمال میکند؛ اما نه برای متد GetOrAdd. زمانیکه valueFactory آن در حال اجرا است، دسترسی همزمان به آن thread-safe نیست و ممکن است بیش از یکبار فراخوانی شود.
یک مثال:
using System; using System.Collections.Concurrent; using System.Threading.Tasks; namespace Sample { class Program { static void Main(string[] args) { var dictionary = new ConcurrentDictionary<int, int>(); var options = new ParallelOptions { MaxDegreeOfParallelism = 100 }; var addStack = new ConcurrentStack<int>(); Parallel.For(1, 1000, options, i => { var key = i % 10; dictionary.GetOrAdd(key, k => { addStack.Push(k); return i; }); }); Console.WriteLine($"dictionary.Count: {dictionary.Count}"); Console.WriteLine($"addStack.Count: {addStack.Count}"); } } }
dictionary.Count: 10 addStack.Count: 13
علت اینجا است که در این بین، متد GetOrAdd توسط ترد A فراخوانی میشود، اما key را در دیکشنری جاری پیدا نمیکند. به همین جهت شروع به اجرای valueFactory آن خواهد کرد. در همین زمان ترد B نیز به دنبال همین key است. ترد قبلی هنوز به پایان کار خودش نرسیدهاست که مجددا valueFactory متعلق به همین key اجرا خواهد شد. به همین جهت است که در ConcurrentStack اجرا شدهی در valueFactory، بیش از 10 آیتم موجود هستند.
الگویی برای مدیریت دسترسی همزمان امن به متد GetOrAdd
یک روش برای دسترسی همزمان امن به متد GetOrAdd، توسط تیم ASP.NET Core به صورت ذیل ارائه شدهاست:
// 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple // threads but only one of the objects succeeds in creating a pipeline. private readonly ConcurrentDictionary<Type, Lazy<RequestDelegate>> _pipelinesCache = new ConcurrentDictionary<Type, Lazy<RequestDelegate>>();
یک مثال:
namespace Sample { class Program { static void Main(string[] args) { var dictionary = new ConcurrentDictionary<int, Lazy<int>>(); var options = new ParallelOptions { MaxDegreeOfParallelism = 100 }; var addStack = new ConcurrentStack<int>(); Parallel.For(1, 1000, options, i => { var key = i % 10; dictionary.GetOrAdd(key, k => new Lazy<int>(() => { addStack.Push(k); return i; })); }); // Access the dictionary values to create lazy values. foreach (var pair in dictionary) Console.WriteLine(pair.Value.Value); Console.WriteLine($"dictionary.Count: {dictionary.Count}"); Console.WriteLine($"addStack.Count: {addStack.Count}"); } } }
10 1 2 3 4 5 6 7 8 9 dictionary.Count: 10 addStack.Count: 10
در این مثال دو تغییر صورت گرفتهاند:
الف) مقادیر ConcurrentDictionary به صورت Lazy معرفی شدهاند.
ب) متد GetOrAdd نیز یک مقدار Lazy را بازگشت میدهد.
زمانیکه از اشیاء Lazy استفاده میشود، خروجیهای بازگشتی از GetOrAdd، توسط این اشیاء Lazy محصور خواهند شد. اما نکتهی مهم اینجا است که هنوز value factory آنها فراخوانی نشدهاست. این فراخوانی تنها زمانی صورت میگیرد که به خاصیت Value یک شیء Lazy دسترسی پیدا کنیم و این دسترسی نیز به صورت thread-safe طراحی شدهاست. یعنی حتی اگر چند ترد new Lazy یک key مشخص را بازگشت دهند، تنها یکبار value factory متد GetOrAdd با دسترسی به خاصیت Value این اشیاء Lazy فراخوانی میشود و مابقی تردها منتظر مانده و تنها مقدار ذخیره شدهی در دیکشنری را دریافت میکنند و سبب اجرای مجدد value factory سنگین و زمانبر آن، نخواهند شد.
بر این مبنا میتوان یک LazyConcurrentDictionary را نیز به صورت ذیل طراحی کرد:
public class LazyConcurrentDictionary<TKey, TValue> { private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _concurrentDictionary; public LazyConcurrentDictionary() { _concurrentDictionary = new ConcurrentDictionary<TKey, Lazy<TValue>>(); } public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) { var lazyResult = _concurrentDictionary.GetOrAdd(key, k => new Lazy<TValue>(() => valueFactory(k), LazyThreadSafetyMode.ExecutionAndPublication)); return lazyResult.Value; } }
در مثال فوق، به صورت صریحی پارامتر LazyThreadSafetyMode نیز مقدار دهی شدهاست. هدف از آن اطمینان حاصل کردن از آغاز این شیء Lazy با دسترسی به خاصیت Value آن، تنها توسط یک ترد است.
نمونهی دیگر کار با خاصیت ویژهی Value شیء Lazy را در مطلب «پشتیبانی توکار از ایجاد کلاسهای Singleton از دات نت 4 به بعد» پیشتر در این سایت مطالعه کردهاید.
سپاس به خاطر موضوع مفیدی که مطرح کردید. این مورد را من در یکی از پروژهها پیاده سازی کردم اما مدتی است که با خطای زیر مواجه میشوم(در لوکال، روی هاست هنوز امتحان نکرده ام)
به نظر شما مشکل از کجا میتواند باشد؟
سشن کلا در پروژه غیر فعال است
iis 10
windows 10
visual studio 2015 و mvc 5
آیا خودتون تابه حال چنین مشکلی داشتهاید؟
- :: ایران آخرین کشور جهان در رتبهبندی سرعت دانلود اینترنت | www.donya-e-eqtesad.com
- ۱۰ نکته کاربردی در طراحی وب سایت | www.webtarget.ir
- آموزش کتابخانه Microsoft Enterprise Library 5.0 - قسمت ششم | www.30sharp.com
- انتشار نگارش جدید ویراستیار (۱٫۳٫۱) و بهروزرسانی ویراستلایو | www.virastyar.ir
- اوراکل افزونههای تجاری را وارد MySQL میکند | azadrah.net
- پروژه هلال احمر SQL | pspcommunity.org
- ریسکهای freelance شدن | blog.afsharm.com
- گشت و گذار در Windows 8 | هر آنچه که ممکن است بخواهید بدانید | www.farsigeek.com
- ویندوز 8، ویندوز توسعه مایکروسافت! ارائه پیش نمایش ویژوال استودیو 2011 و دات نت فریم ورک 4.5 | www.persiadevelopers.com
- A Look At Robotics Developer Studio 4 Beta | channel9.msdn.com
- British Schoolkids To Be Taught Computer Coding | rss.slashdot.org
- Deploying a Site With Git Hooks | wekeroad.com
- Download all the Build Videos with RSS | geekswithblogs.net
- Improvements in the CLR Core in .NET Framework 4.5 | blogs.microsoft.co.il
- Internet Explorer 9 and 10 now fully pass Acid3 test thanks to criteria changes | www.winrumors.com
- My thoughts about Build, Windows 8, WinRT, XAML and Silverlight | geekswithblogs.net
- SourceMonitor V3.1.5.190 released | geekswithblogs.net
- SQL 11 (Code Name: “Denali”) – Debugging enhancements – Setting, Labeling, Using & Searching Conditional Breakpoints | beyondrelational.com
- Visual Studio 11 Developer Preview | dotnetslackers.com
- What’s new in WCF 4.5? a single WSDL file | blogs.microsoft.co.il