نظرات مطالب
غنی سازی کامپایلر C# 9.0 با افزونهها
یک نکتهی تکمیلی
security-code-scan را هم میتوان به عنوان یک افزونهی بررسی مسایل امنیتی، به مجموعهی تنظیمات فوق اضافه کرد:
<PackageReference Include="SecurityCodeScan" Version="3.5.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
بعد از افزودن این هدرهای امنیتی خطای زیر رو دریافت میکنم و تصاویری که از خارج از پروژه باشه نمیتونه بارگذاری کنه
Content Security Policy: The page’s settings blocked the loading of a resource at https://mdbootstrap.com/img/Photos/Others/sidenav3.jpg (“img-src”).
نحوهی نصب و راه اندازی برنامههای ASP.NET Core را در IIS، پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بررسی کردیم. در این مطلب میخواهیم به تعدادی از خطاهای ممکن در حین راه اندازی اولیهی این نوع برنامهها بپردازیم.
خطای 500.19
این خطا زمانی رخ میدهد که ماژول هاستینگ ASP.NET Core، توسط IIS شناسایی نشده باشد. نصب مجدد آن این مشکل را برطرف میکند.
لیست تمام ماژولهای هاستینگ را همواره در اینجا میتوانید پیدا کنید.
خطای 502.5 و یا گاهی از اوقات 502
باید دقت داشته باشید که اگر تنظیم disableStartUpErrorPage در IIS فعال باشد (قابل افزودن به تگ aspNetCore تنظیمات وب کانفیگ ذیل)، صرفا خطای 502 را دریافت میکنید.
این خطا به معنای شکست در اجرای ماژول هاستینگ ASP.NET Core است و ممکن است به یکی از دلایل ذیل ایجاد شده باشد:
الف) در حین اجرای برنامهی شما، استثنایی در کدهای فایل آغازین startup.cs برنامه، رخ دادهاست.
ب) پورت مورد استفادهی برنامه، توسط پروسهی دیگری در حال استفاده است.
ج) برنامهی شما برای SDK با نگارش 1.1.2 تنظیم و کامپایل شدهاست؛ اما بر روی سرور حداکثر، SDK نگارش 1.1.1 نصب شدهاست.
د) ممکن است پروسهی IIS قادر به یافتن و حتی اجرای dotnet.exe نباشد.
برای لاگ کردن مورد «الف»، باید لاگ کردن خطاهای برنامه را در web.config آن فعالسازی کنید:
چند نکته:
- اگر این مورد به مسیر logs\stdout\. تنظیم شدهاست، باید پوشهی logs را در ریشهی پروژه به صورت دستی ایجاد کنید؛ و گرنه IIS آنرا به صورت خودکار ایجاد نخواهد کرد.
- کاربر App Pool برنامه (با نام پیشفرض « IIS AppPool\DefaultAppPool») باید دسترسی نوشتن در این پوشه را داشته باشد؛ وگرنه فایل لاگی در آن ایجاد نخواهد شد.
- همچنین اگر با رعایت تمام این موارد، محتوای این فایل تولید شده باز هم خالی بود، یکبار IIS را ریاستارت کنید. ممکن است IIS کار نوشتن در فایل لاگ را تمام نکرده باشد و با این کار مجبور به تکمیل و بستن فایل میشود.
- برای حالت «ب» قبل از هر تغییری، یکبار کل سرور را ریاستارت کنید.
- برای مورد «ج» نیز باید آخرین SDK هاستینگ را بر روی سرور نصب کنید.
لیست تمام SDKهای نصب شدهی بر روی سیستم را در مسیر «C:\Program Files\dotnet\sdk» میتوانید مشاهده کنید. همچنین دستور «dotnet --list-sdks» نیز لیست SDKهای نصب شده را نمایش میدهد.
- برای رفع حالت «د»، نیاز است این موارد را بررسی کنید:
1- «Load User Profile» را به true تنظیم کنید.
برای اینکار به قسمت Application pools مراجعه کرده و تنظیمات پیشرفتهی App pool مورد استفاده را ویرایش کنید (تصویر فوق).
این تنظیم برای دائمی کردن کلیدهای رمزنگاری برنامههای ASP.NET Core نیز ضروری است و باید جزو چک لیست نصب برنامههای ASP.NET Core قرار گیرد.
2- مورد «د» حتی میتواند به علت عدم تعریف مسیر «C:\Program Files\dotnet\» در path ویندوز باشد. برای این منظور دستور env:path$ را در power shell اجرا کنید و بررسی کنید که آیا این مسیر در خروجی آن موجود است یا خیر؟ اگر نبود، پس از اضافه کردن آن به path ویندوز، باید یکبار IIS را هم ریست کنید تا این تنظیمات جدید را بخواند.
3- مورد «د» ممکن است به علت اشتباه تنظیم پوشهی اصلی برنامه در IIS نیز باشد. یعنی dotnet.exe قادر به یافتن اسمبلیهای برنامه نیست.
4- برای رفع مورد «د» دو دسترسی دیگر را نیز باید بررسی کنید:
الف) آیا کاربر Application pool برنامه به پوشهی برنامه دسترسی read & execute را دارد یا خیر؟
ب) آیا کاربر Application pool برنامه به پوشهی C:\Program Files\dotnet دسترسی read & execute را دارد یا خیر؟
اگر خیر، نحوهی دسترسی دادن به آنها به صورت زیر است:
خطای 502.3 و یا گاهی از اوقات 500
این خطا به صورت خلاصه به معنای «Bad Gateway: Forwarder Connection Error» است و زمانی رخ میدهد که پروسهی dotnet.exe به درخواست رسیده شده یا پاسخی ندادهاست (مشاهده خطای 0x80072EE2 یا ERROR_WINHTTP_TIMEOUT) و یا بیش از اندازه این پاسخ دهی طول کشیدهاست (این تنظیمات را در configuration editor میتوانید مشاهده کنید که در حقیقت همان تگ aspNetCore در تنظیمات وب کانفیگ فوق است).
برای دیباگ بهتر این مورد نیاز است علاوه بر تنظیم web.config فوق، به فایل appsettings.json مراجعه کرده و سطح پیش فرض لاگ کردن اطلاعات را که warning است به information تغییر دهید:
در این حالت درخواستی که پردازش نشدهاست نیز در لاگها حضور خواهد داشت و ممکن است این درخواست به علت عدم تنظیم CORS بدون پاسخ باقی مانده باشد.
و یا اگر پردازشی دارید که بیش از 2 دقیقه طول میکشد (مطابق تنظیمات تصویر فوق)، میتوانید مقدار request time out را بیشتر کنید.
خطای 0x80004005 : 80008083
خطای 0x80008083 به معنای تداخل نگارشها است و خطای 0x80004005 به معنای مفقود بودن یک فایل یا عدم دسترسی به آن است.
این خطا زمانی رخ میدهد که برنامهی خود را ارتقاء داده باشید، اما ماژول هاستینگ ASP.NET Core را بر روی سرور به روز رسانی نکرده باشید.
خطای 500.19
اگر برنامهی شما از امکانات URL Rewrite خود IIS استفاده میکند، عدم نصب بودن آن بر روی سرور، این خطا را سبب خواهد شد.
برای اینکار ابتدا IIS را متوقف کنید. سپس SDK جدید را نصب و پس از آن IIS را مجددا راه اندازی نمائید.
خطای 503
برنامه اجرا نشده و سطر ذیل در Event Viewer ویندوز قابل مشاهده است:
اگر اخیرا سیستم عامل را ارتقاء دادهاید، ممکن است این خطا را دریافت کنید. راه حل آن نصب مجدد ماژول هاستینگ ASP.NET Core است تا نصب قبلی تعمیر شود.
خطای 500.19
این خطا زمانی رخ میدهد که ماژول هاستینگ ASP.NET Core، توسط IIS شناسایی نشده باشد. نصب مجدد آن این مشکل را برطرف میکند.
لیست تمام ماژولهای هاستینگ را همواره در اینجا میتوانید پیدا کنید.
خطای 502.5 و یا گاهی از اوقات 502
باید دقت داشته باشید که اگر تنظیم disableStartUpErrorPage در IIS فعال باشد (قابل افزودن به تگ aspNetCore تنظیمات وب کانفیگ ذیل)، صرفا خطای 502 را دریافت میکنید.
این خطا به معنای شکست در اجرای ماژول هاستینگ ASP.NET Core است و ممکن است به یکی از دلایل ذیل ایجاد شده باشد:
الف) در حین اجرای برنامهی شما، استثنایی در کدهای فایل آغازین startup.cs برنامه، رخ دادهاست.
ب) پورت مورد استفادهی برنامه، توسط پروسهی دیگری در حال استفاده است.
ج) برنامهی شما برای SDK با نگارش 1.1.2 تنظیم و کامپایل شدهاست؛ اما بر روی سرور حداکثر، SDK نگارش 1.1.1 نصب شدهاست.
د) ممکن است پروسهی IIS قادر به یافتن و حتی اجرای dotnet.exe نباشد.
برای لاگ کردن مورد «الف»، باید لاگ کردن خطاهای برنامه را در web.config آن فعالسازی کنید:
<system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\MyApp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true" /> </system.webServer>
- اگر این مورد به مسیر logs\stdout\. تنظیم شدهاست، باید پوشهی logs را در ریشهی پروژه به صورت دستی ایجاد کنید؛ و گرنه IIS آنرا به صورت خودکار ایجاد نخواهد کرد.
- کاربر App Pool برنامه (با نام پیشفرض « IIS AppPool\DefaultAppPool») باید دسترسی نوشتن در این پوشه را داشته باشد؛ وگرنه فایل لاگی در آن ایجاد نخواهد شد.
- همچنین اگر با رعایت تمام این موارد، محتوای این فایل تولید شده باز هم خالی بود، یکبار IIS را ریاستارت کنید. ممکن است IIS کار نوشتن در فایل لاگ را تمام نکرده باشد و با این کار مجبور به تکمیل و بستن فایل میشود.
- برای حالت «ب» قبل از هر تغییری، یکبار کل سرور را ریاستارت کنید.
- برای مورد «ج» نیز باید آخرین SDK هاستینگ را بر روی سرور نصب کنید.
لیست تمام SDKهای نصب شدهی بر روی سیستم را در مسیر «C:\Program Files\dotnet\sdk» میتوانید مشاهده کنید. همچنین دستور «dotnet --list-sdks» نیز لیست SDKهای نصب شده را نمایش میدهد.
- برای رفع حالت «د»، نیاز است این موارد را بررسی کنید:
1- «Load User Profile» را به true تنظیم کنید.
برای اینکار به قسمت Application pools مراجعه کرده و تنظیمات پیشرفتهی App pool مورد استفاده را ویرایش کنید (تصویر فوق).
این تنظیم برای دائمی کردن کلیدهای رمزنگاری برنامههای ASP.NET Core نیز ضروری است و باید جزو چک لیست نصب برنامههای ASP.NET Core قرار گیرد.
2- مورد «د» حتی میتواند به علت عدم تعریف مسیر «C:\Program Files\dotnet\» در path ویندوز باشد. برای این منظور دستور env:path$ را در power shell اجرا کنید و بررسی کنید که آیا این مسیر در خروجی آن موجود است یا خیر؟ اگر نبود، پس از اضافه کردن آن به path ویندوز، باید یکبار IIS را هم ریست کنید تا این تنظیمات جدید را بخواند.
3- مورد «د» ممکن است به علت اشتباه تنظیم پوشهی اصلی برنامه در IIS نیز باشد. یعنی dotnet.exe قادر به یافتن اسمبلیهای برنامه نیست.
4- برای رفع مورد «د» دو دسترسی دیگر را نیز باید بررسی کنید:
الف) آیا کاربر Application pool برنامه به پوشهی برنامه دسترسی read & execute را دارد یا خیر؟
ب) آیا کاربر Application pool برنامه به پوشهی C:\Program Files\dotnet دسترسی read & execute را دارد یا خیر؟
اگر خیر، نحوهی دسترسی دادن به آنها به صورت زیر است:
Right click on the folder -> Properties -> Security tab -> Click at Edit button -> Enter `IIS AppPool\DefaultAppPool` user (IIS AppPool\<app_pool_name>) -> Click at Check names -> OK -> Then give it `read & execute` or other permissions.
خطای 502.3 و یا گاهی از اوقات 500
این خطا به صورت خلاصه به معنای «Bad Gateway: Forwarder Connection Error» است و زمانی رخ میدهد که پروسهی dotnet.exe به درخواست رسیده شده یا پاسخی ندادهاست (مشاهده خطای 0x80072EE2 یا ERROR_WINHTTP_TIMEOUT) و یا بیش از اندازه این پاسخ دهی طول کشیدهاست (این تنظیمات را در configuration editor میتوانید مشاهده کنید که در حقیقت همان تگ aspNetCore در تنظیمات وب کانفیگ فوق است).
برای دیباگ بهتر این مورد نیاز است علاوه بر تنظیم web.config فوق، به فایل appsettings.json مراجعه کرده و سطح پیش فرض لاگ کردن اطلاعات را که warning است به information تغییر دهید:
"Console": { "LogLevel": { "Default": "Information" } }
و یا اگر پردازشی دارید که بیش از 2 دقیقه طول میکشد (مطابق تنظیمات تصویر فوق)، میتوانید مقدار request time out را بیشتر کنید.
خطای 0x80004005 : 80008083
Application ‘<IIS path>’ with physical root ‘<Application path>’ failed to start process with commandline ‘”dotnet” .\MyApp.dll’, ErrorCode = ‘0x80004005 : 80008083.
این خطا زمانی رخ میدهد که برنامهی خود را ارتقاء داده باشید، اما ماژول هاستینگ ASP.NET Core را بر روی سرور به روز رسانی نکرده باشید.
خطای 500.19
HTTP Error 500.19 - Internal Server Error The requested page cannot be accessed because the related configuration data for the page is invalid.
برای اینکار ابتدا IIS را متوقف کنید. سپس SDK جدید را نصب و پس از آن IIS را مجددا راه اندازی نمائید.
خطای 503
برنامه اجرا نشده و سطر ذیل در Event Viewer ویندوز قابل مشاهده است:
The Module DLL C:\WINDOWS\system32\inetsrv\aspnetcore.dll failed to load. The data is the error.
یکی از مهمترین قسمتهای مدل سازی موجودیتها، تعیین نوعهای صحیح ستونها و همچنین تعیین اندازهی مناسبی برای آنها است؛ به همراه تعیین اجباری بودن یا نبودن مقدار دهی آنها.
تعیین اجباری بودن یا نبودن ستونها در EF Core
به صورت پیش فرض در EF Core، هر نوع CLR ایی که نال پذیر باشد، به صورت یک ستون اختیاری در نظر گرفته میشود؛ مانند:
و هر ستونی که نوع CLR آن نال پذیر نباشد، مقدار دهی آن در EF Core اجباری است؛ مانند:
همچنین باید دقت داشت که حتی اگر در تنظیمات نگاشتهای برنامه به صورت اختیاری تعریف شوند، باز هم EF Core آنها را اجباری درنظر میگیرد.
برای لغو اختیاری بودن یک خاصیت نال پذیر میتوان از ویژگی Required استفاده کرد:
نوع string نال پذیر است. برای لغو این وضعیت میتوان از ویژگی Required استفاده کرد که در سمت بانک اطلاعاتی نیز به not null ترجمه میشود.
و یا معادل Fluent API آن با استفاده از ذکر متد IsRequired است:
با توجه به این توضیحات، نیازی نیست در بالای یک خاصیت از نوع int، ویژگی Required را ذکر کرد. چون int نال پذیر نیست، مقدار دهی آن اجباری است.
کار با رشتهها در EF Core
ذکر یک خاصیت رشتهای به این صورت:
به معنای نال پذیر بودن این ستون است (چون Required تعریف نشدهاست) و همچنین نوع و طول آن در SQL Server به nvarchar max تنظیم میشود. این تنظیم طول هرچند در مورد SQL Server صادق است، اما ممکن است در SQL Server CE به nvarchar 4000 تفسیر شود (و این مشکل را به همراه داشته باشد که چرا نمیتوان متون طولانی را در آن ثبت کرد). به عبارتی عدم ذکر دقیق طول یک خاصیت رشتهای، در پروایدرهای مختلف، ممکن است معانی مختلفی را به همراه داشته باشد. بنابراین نیاز است طول خواص رشتهای حتما ذکر شوند تا در تمام بانکهای اطلاعاتی با دقت کامل و بدون حدس و گمان تنظیم گردند.
برای تعیین طول دقیق یک فیلد رشتهای، میتوان از ویژگیهای StringLength و یا MaxLength با ذکر اندازهای استفاده کرد.
برای تعیین صریح یک فیلد رشتهای به حداکثر مقدار آن بهتر است ویژگی MaxLength را بدون ذکر پارامتری قید کرد. این مورد جهت سازگاری با بانکهای اطلاعاتی مختلف ضروری است.
معادل این تنظیمات با روش Fluent API به صورت زیر است:
برای تعیین صریح طول یک فیلد رشتهای:
و برای تعیین صریح nvarchar max بودن آن فیلد:
حالت پیش فرض EF Core، کار با رشتههای یونیکد است. یعنی تمام فیلدهای فوق به nvarchar تفسیر میشوند و این n ایی که در ابتدا ذکر شدهاست به معنای یونیکد بودن آن است. اگر میخواهید این پیشفرض تعیین نوع یونیکد را تغییر دهید، میتوان از ویژگی Column استفاده کرد:
البته اگر اطلاعاتی را که با آن کار میکنید چندزبانی و یونیکد هستند، بهتر است این مورد را تغییر ندهید.
نکتهای در مورد تغییر نوع خواص: اگر از متد HasColumnType و یا ویژگی Column به نحو فوق استفاده کردید، نیاز است طول رشته را صریحا مشخص کنید. در غیر اینصورت در حین migration خطای ذیل را دریافت خواهید کرد:
در اینجا عنوان میکند که اگر مقصود شما varchar max است، ویژگی MaxLength را حذف کرده و تنها بنویسید:
نکتهای در مورد ایندکسها: در قسمت قبل عنوان شد که میتوان بر روی خواص، ایندکس منحصربفرد اعمال کرد. در مورد رشتهها در SQL Server، اگر طول فیلد مدنظر حداکثر تا 900 بایت باشد، یک چنین کاری را میتوان انجام داد. البته این محدودیت 900 بایتی تا SQL Server 2014 وجود دارد. این سقف در SQL Server 2016 به 1700 بایت افزایش یافتهاست (900bytes for a clustered index. 1,700 for a nonclustered index). بنابراین چون نوع پیش فرض ستونهای رشتهای، یونیکد و nvarchar درنظر گرفته میشود، حداکثر طول امنی را که میتوان برای آن تعریف کرد، مساوی 450 است (نصف 900 بایت). به همین جهت ذکر ایندکس منحصربفرد بر روی یک ستون رشتهای، باید به همراه ذکر اجباری حداکثر طول مساوی 450 آن باشد.
کار با اعداد در EF Core
کلاس نمونهای را با ساختار ذیل درنظر بگیرید:
پس از اعمال مهاجرتها و به روز رسانی ساختار بانک اطلاعاتی، به ساختار ذیل خواهیم رسید:
همانطور که ملاحظه میکنید، نوع bool دات نت به نوع bit در SQL Server، نوع long به bigint، نوع short به smallint، نوع int به int و نوع byte به tinyint نگاشت شدهاند.
نکتهای در مورد اعداد اعشاری: توصیه شدهاست در تعاریف موجودیتهای خود بهتر است از نوعهای float و یا double استفاده نکنید. برای کار با اعداد اعشاری از نوع decimal استفاده نمائید تا بتوانید از قابلیت مقایسهی دقیق آنها استفاده کنید. اطلاعات بیشتر: «روش صحیح مقایسه دو عدد اعشاری با هم»
کار با تاریخ در EF Core
اگر به تصویر فوق دقت کنید، نوع DateTime دات نت به datetime2 در سمت SQL Server ترجمه شدهاست:
اگر در دادههای خود نیازی به زمان ندارید، میتوان این نوع پیش فرض را با ویژگی Column که پیشتر بحث شد، به date تغییر داد.
اطلاعات بیشتر: «کنترل نوعهای داده با استفاده از EF در SQL Server»
به علاوه در دات نت نوع DateTime از نوع value type است. بنابراین همانطور که در ابتدای بحث نیز عنوان شد، مقدار دهی آن اجباری است؛ مگر آنکه آنرا نال پذیر تعریف کنید.
کار با مباحث همزمانی در EF Core
EF Core به صورت پیش فرض، فرض میکند رکوردی را که با آن در حال کار هستید، توسط هیچ کاربر دیگری در شبکه تغییر نیافتهاست و تغییرات شما را در حین فراخوانی متد SaveChanges ذخیره میکند. اگر علاقمند هستید که EF Core در صورت تغییر مقدار خاصیت خاصی توسط سایر کاربران، این مساله را با صدور استثنایی به شما اطلاع رسانی کند، از ویژگی ConcurrencyCheck
و یا متد IsConcurrencyToken حالت Fluent API استفاده نمائید:
در این حالت کوئری به روز رسانی، علاوه بر فیلد Id رکورد، حاوی فیلد Name نیز خواهد بود (در حین تشکیل شرط یافتن رکورد) و اگر در بین فاصلهی یافتن شخص و به روز رسانی نام او، شخص دیگری اینکار را انجام داده باشد، این به روز رسانی موفقیت آمیز نبوده و استثنایی صادر میشود.
اگر علاقمند هستید که تمام فیلدهای جدول تحت نظر قرارگیرند، میتوان از روش ویژهای به نام Timestamp/row version استفاده کرد:
با معادل Fluent API ذیل:
در مورد ValueGeneratedOnAddOrUpdate در قسمت قبل بحث کردیم. فیلد TimeStamp نیز جزو فیلدهای ویژهای است که SQL Server به صورت خودکار قادر است آنرا مقدار دهی کند و زمانیکه ValueGeneratedOnAddOrUpdate قید میشود، یعنی این فیلد همواره با فراخوانی متد SaveChanges، به صورت خودکار مقدار دهی خواهد شد (و نیازی نیست تا توسط برنامه مقدار دهی شود).
در این حالت در حین به روز رسانی یک چنین رکوردی، اگر از زمان کوئری آن (یافتن رکورد) و ذخیره سازی آن، شخص دیگری آنرا تغییر داده باشد، به علت عدم تطابق Timestamp ها، عملیات به روز رسانی باشکست روبرو شده و یک استثناء صادر میشود.
تعیین اجباری بودن یا نبودن ستونها در EF Core
به صورت پیش فرض در EF Core، هر نوع CLR ایی که نال پذیر باشد، به صورت یک ستون اختیاری در نظر گرفته میشود؛ مانند:
string, int?, byte[]
int, decimal, bool, DateTime
برای لغو اختیاری بودن یک خاصیت نال پذیر میتوان از ویژگی Required استفاده کرد:
[Required] public string Url { get; set; }
و یا معادل Fluent API آن با استفاده از ذکر متد IsRequired است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); }
کار با رشتهها در EF Core
ذکر یک خاصیت رشتهای به این صورت:
public string FirstName { get; set; }
[StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } [MaxLength] public string Address { get; set; }
برای تعیین صریح یک فیلد رشتهای به حداکثر مقدار آن بهتر است ویژگی MaxLength را بدون ذکر پارامتری قید کرد. این مورد جهت سازگاری با بانکهای اطلاعاتی مختلف ضروری است.
معادل این تنظیمات با روش Fluent API به صورت زیر است:
برای تعیین صریح طول یک فیلد رشتهای:
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasMaxLength(450);
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasColumnType("nvarchar(max)");
[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; }
نکتهای در مورد تغییر نوع خواص: اگر از متد HasColumnType و یا ویژگی Column به نحو فوق استفاده کردید، نیاز است طول رشته را صریحا مشخص کنید. در غیر اینصورت در حین migration خطای ذیل را دریافت خواهید کرد:
Data type 'varchar' is not supported in this form. Either specify the length explicitly in the type name, for example as 'varchar(16)', or remove the data type and use APIs such as HasMaxLength to allow EF choose the data type.
[Column(TypeName = "varchar(max)")]
نکتهای در مورد ایندکسها: در قسمت قبل عنوان شد که میتوان بر روی خواص، ایندکس منحصربفرد اعمال کرد. در مورد رشتهها در SQL Server، اگر طول فیلد مدنظر حداکثر تا 900 بایت باشد، یک چنین کاری را میتوان انجام داد. البته این محدودیت 900 بایتی تا SQL Server 2014 وجود دارد. این سقف در SQL Server 2016 به 1700 بایت افزایش یافتهاست (900bytes for a clustered index. 1,700 for a nonclustered index). بنابراین چون نوع پیش فرض ستونهای رشتهای، یونیکد و nvarchar درنظر گرفته میشود، حداکثر طول امنی را که میتوان برای آن تعریف کرد، مساوی 450 است (نصف 900 بایت). به همین جهت ذکر ایندکس منحصربفرد بر روی یک ستون رشتهای، باید به همراه ذکر اجباری حداکثر طول مساوی 450 آن باشد.
کار با اعداد در EF Core
کلاس نمونهای را با ساختار ذیل درنظر بگیرید:
public class Person { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } [StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } //[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; } //bit public bool IsActive { get; set; } //tiny Int public byte Age { get; set; } //small Int public short Short { get; set; } //int public int Int32 { get; set; } //Big int public long Long { get; set; } }
همانطور که ملاحظه میکنید، نوع bool دات نت به نوع bit در SQL Server، نوع long به bigint، نوع short به smallint، نوع int به int و نوع byte به tinyint نگاشت شدهاند.
نکتهای در مورد اعداد اعشاری: توصیه شدهاست در تعاریف موجودیتهای خود بهتر است از نوعهای float و یا double استفاده نکنید. برای کار با اعداد اعشاری از نوع decimal استفاده نمائید تا بتوانید از قابلیت مقایسهی دقیق آنها استفاده کنید. اطلاعات بیشتر: «روش صحیح مقایسه دو عدد اعشاری با هم»
کار با تاریخ در EF Core
اگر به تصویر فوق دقت کنید، نوع DateTime دات نت به datetime2 در سمت SQL Server ترجمه شدهاست:
CREATE TABLE [dbo].[Persons]( [DateAdded] [datetime2](7) NULL, [DateUpdated] [datetime2](7) NULL,
اطلاعات بیشتر: «کنترل نوعهای داده با استفاده از EF در SQL Server»
به علاوه در دات نت نوع DateTime از نوع value type است. بنابراین همانطور که در ابتدای بحث نیز عنوان شد، مقدار دهی آن اجباری است؛ مگر آنکه آنرا نال پذیر تعریف کنید.
کار با مباحث همزمانی در EF Core
EF Core به صورت پیش فرض، فرض میکند رکوردی را که با آن در حال کار هستید، توسط هیچ کاربر دیگری در شبکه تغییر نیافتهاست و تغییرات شما را در حین فراخوانی متد SaveChanges ذخیره میکند. اگر علاقمند هستید که EF Core در صورت تغییر مقدار خاصیت خاصی توسط سایر کاربران، این مساله را با صدور استثنایی به شما اطلاع رسانی کند، از ویژگی ConcurrencyCheck
[ConcurrencyCheck] public string Name { set; get; }
modelBuilder.Entity<Person>() .Property(p => p.Name) .IsConcurrencyToken();
اگر علاقمند هستید که تمام فیلدهای جدول تحت نظر قرارگیرند، میتوان از روش ویژهای به نام Timestamp/row version استفاده کرد:
[Timestamp] public byte[] Timestamp { get; set; }
modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .ValueGeneratedOnAddOrUpdate() .IsConcurrencyToken();
در این حالت در حین به روز رسانی یک چنین رکوردی، اگر از زمان کوئری آن (یافتن رکورد) و ذخیره سازی آن، شخص دیگری آنرا تغییر داده باشد، به علت عدم تطابق Timestamp ها، عملیات به روز رسانی باشکست روبرو شده و یک استثناء صادر میشود.
مطالب
خواندنیهای 7 خرداد
- Velocity CTP3 ارائه شد.
پروژه سورس بازی است که از بسیاری از ماژولهای جدید دات نت فریم ورک استفاده میکند و نکتهی جالب نگارش جدید آن مهاجرت از MS Ajax به jQuery Ajax است (قسمت اجکس آن کلا از صفر بر اساس jQuery بازنویسی شده است).
- چک لیستی که پیش از برپایی یک وب سایت باید بررسی شود.
- نگارش بتا Ext Core 3.0 ارائه شد.
- یک سری برگههای مرجع لینوکسی!
- دمویی در مورد ویندوز Azure و سرویسهای مبتنی بر آن
مطالب
OpenCVSharp #5
استفاده از پنجرهی native خود OpenCV، روش مرسومی است در زبانهای مختلف برنامه نویسی که از OpenCV استفاده میکنند و این پنجره مستقل است از سکوی کاری مورد استفاده. اما شاید در دات نت علاقمند باشید که نتیجهی عملیات را در یک picture box استاندارد نمایش دهید. در ادامه، تبدیل تصاویر OpenCV را به فرمت دات نت، در دو قالب برنامههای WinForms و همچنین WPF، بررسی خواهیم کرد.
استفاده از OpenCVSharp در برنامههای WinForms به کمک PictureBoxIpl
یکی از اسمبلیهای کتابخانهی OpenCVSharp را که در پوشهی bin برنامه میتوان مشاهده کرد، OpenCvSharp.UserInterface.dll نام دارد. این اسمبلی حاوی یک picture box جدید به نام PictureBoxIpl است که میتواند تصاویری را با فرمت IplImage، دریافت کند.
میتوانید این picture box ویژه را از طریق منوی ToolBox -> Choose items و سپس صفحهی دیالوگ فوق، به نوار ابزار WinForms اضافه کرده و از آن استفاده کنید و یا میتوان با کدنویسی نیز به آن دسترسی یافت:
در اینجا تصویر مورد نظر را توسط کلاس IplImage بارگذاری کرده و سپس برای نمونه فیلتر Dilate را به آن اعمال کردهایم. سپس وهلهی جدیدی از کنترل PictureBoxIpl ایجاد و خاصیت ImageIpl آن، به تصویر بارگذاری شده، تنظیم و در آخر این picture box با کدنویسی به صفحه اضافه شدهاست.
یک نکته
هر نوع تغییری به iplImage پس از انتساب آن به خاصیت ImageIpl، نمایش داده نخواهد شد. برای به حداقل رساندن سربار ایجاد اشیاء جدید (خصوصا برای نمایش اطلاعات رسیدهی از دوربین یا WebCam)، از متد RefreshIplImage استفاده کنید. این متد بجای ایجاد یک شیء جدید، تنها ناحیهی موجود را مجددا ترسیم خواهد کرد و بسیار سریع است:
استفاده از OpenCVSharp در برنامههای WinForms به کمک PictureBox
اگر نخواهید از کنترل جدید PictureBoxIpl استفاده کنید، میتوان از همان Picture box استاندارد WinForms نیز کمک گرفت:
تنها نکتهای که در اینجا جدید است، استفاده از متد الحاقی ToBitmap میباشد که در کلاس BitmapConverter کتابخانهی OpenCVSharp تعریف شدهاست. به این ترتیب تصویر با فرمت OpenCV، به یک Bitmap دات نتی تبدیل میشود. اکنون میتوان این بیتمپ را برای مثال به یک Picture box استاندارد انتساب داد و یا حتی متد Save آنرا فراخوانی کرد و آنرا بر روی دیسک سخت، ذخیره نمود.
یک نکته
در اینجا نیز برای به حداقل رسانی به روز رسانیهای بعدی picture box بهتر است از متد ToBitmap به شکل زیر کمک گرفت:
به این ترتیب سربار وهله سازی یک شیء جدید Bitmap حذف خواهد شد و صرفا ناحیهی نمایشی مجددا ترسیم میشود.
استفاده از OpenCVSharp در برنامههای WPF
در WPF میتوان با استفاده از متد الحاقی ToWriteableBitmap کلاس BitmapConverter، فرمت IplImage را به منبع تصویر یک کنترل تصویر استاندارد، تبدیل کرد:
کدهای کامل WPF و WinForms این مطلب برای دریافت.
استفاده از OpenCVSharp در برنامههای WinForms به کمک PictureBoxIpl
یکی از اسمبلیهای کتابخانهی OpenCVSharp را که در پوشهی bin برنامه میتوان مشاهده کرد، OpenCvSharp.UserInterface.dll نام دارد. این اسمبلی حاوی یک picture box جدید به نام PictureBoxIpl است که میتواند تصاویری را با فرمت IplImage، دریافت کند.
میتوانید این picture box ویژه را از طریق منوی ToolBox -> Choose items و سپس صفحهی دیالوگ فوق، به نوار ابزار WinForms اضافه کرده و از آن استفاده کنید و یا میتوان با کدنویسی نیز به آن دسترسی یافت:
using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color)) { Cv.Dilate(iplImage, iplImage); var pictureBoxIpl = new OpenCvSharp.UserInterface.PictureBoxIpl { ImageIpl = iplImage, AutoSize = true }; flowLayoutPanel1.Controls.Add(pictureBoxIpl); }
یک نکته
هر نوع تغییری به iplImage پس از انتساب آن به خاصیت ImageIpl، نمایش داده نخواهد شد. برای به حداقل رساندن سربار ایجاد اشیاء جدید (خصوصا برای نمایش اطلاعات رسیدهی از دوربین یا WebCam)، از متد RefreshIplImage استفاده کنید. این متد بجای ایجاد یک شیء جدید، تنها ناحیهی موجود را مجددا ترسیم خواهد کرد و بسیار سریع است:
pictureBoxIpl.RefreshIplImage(iplImage);
استفاده از OpenCVSharp در برنامههای WinForms به کمک PictureBox
اگر نخواهید از کنترل جدید PictureBoxIpl استفاده کنید، میتوان از همان Picture box استاندارد WinForms نیز کمک گرفت:
Bitmap bitmap; using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color)) { bitmap = iplImage.ToBitmap(); // BitmapConverter.ToBitmap() } var pictureBox = new PictureBox { Image = bitmap, ClientSize = bitmap.Size }; flowLayoutPanel1.Controls.Add(pictureBox);
یک نکته
در اینجا نیز برای به حداقل رسانی به روز رسانیهای بعدی picture box بهتر است از متد ToBitmap به شکل زیر کمک گرفت:
iplImage.ToBitmap(dst: (Bitmap)pictureBox.Image);
استفاده از OpenCVSharp در برنامههای WPF
در WPF میتوان با استفاده از متد الحاقی ToWriteableBitmap کلاس BitmapConverter، فرمت IplImage را به منبع تصویر یک کنترل تصویر استاندارد، تبدیل کرد:
using System.Windows.Media; using OpenCvSharp; using OpenCvSharp.Extensions; namespace OpenCVSharpSample05Wpf { public partial class MainWindow { public MainWindow() { InitializeComponent(); loadImage(); } private void loadImage() { using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color)) { Cv.Dilate(iplImage, iplImage); Image1.Source = iplImage.ToWriteableBitmap(PixelFormats.Bgr24); } } } }
کدهای کامل WPF و WinForms این مطلب برای دریافت.
مطالب دورهها
آشنایی با مدل برنامه نویسی TAP
تاریخچهی اعمال غیر همزمان در دات نت فریم ورک
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
در این مثال متداول، متد DownloadString به صورت همزمان یا synchronous عمل میکند. به این معنا که تا پایان عملیات دریافت اطلاعات از وب، منتظر مانده و ترد جاری را قفل میکند. مشکل از جایی آغاز میشود که مدت زمان دریافت اطلاعات، طولانی باشد. چون این عملیات در ترد UI در حال انجام است، کل رابط کاربری برنامه تا پایان عملیات نیز قفل شده و دیگر پاسخگوی سایر اعمال رسیده نخواهد بود. در این حالت عموما ویندوز در نوار عنوان برنامه، واژههای Not responding را نمایش میدهد.
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
در اینجا همچنین توسط آرگومان DownloadStringCompletedEventArgs، موفقیت یا شکست عملیات نیز گزارش میشود و مقدار e.Result حاصل عملیات است.
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
در اینجا از روش دیگری برای دریافت اطلاعات از وب استفاده کردهایم. با استفاده از امکانات HttpWebRequest، کوئریهای پیشرفتهتری را میتوان تهیه کرد. برای مثال میتوان نوع متد را به HEAD تنظیم نمود؛ تا صرفا مقادیر هدر آدرس درخواستی از سرور، دریافت شوند.
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
علت اینجا است که asyncResult دریافتی، در تردی دیگر نسبت به ترد اصلی برنامه که UI را اداره میکند، اجرا میشود. یکی از راه حلهای این مشکل و انتقال اطلاعات به ترد اصلی برنامه، استفاده از Synchronization Context است:
SynchronizationContext.Current در اینجا چون در ابتدای متد دریافت اطلاعات اجرا میشود، به ترد UI، یا ترد اصلی برنامه اشاره میکند. به همین جهت این زمینه را نباید داخل Async callback دریافت کرد؛ زیرا ترد جاری آن، ترد UI مدنظر ما نیست. سپس همانطور که ملاحظه میکنید، توسط متد Post آن میتوان اطلاعات را در زمینهی تردی که SynchronizationContext به آن اشاره میکند اجرا کرد.
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
شیوهی نگارش آن بر اساس راهنمای نوشتن برنامههای Async یا Task asynchronous programming model یا به اختصار TAP است:
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
در این متد با استفاده از Task.Delay، انجام یک عملیات طولانی شبیه سازی شدهاست؛ مثلا دریافت یک عدد یا نتیجه از یک وب سرویس. سپس در نهایت، عددی را بازگشت داده است. برای بازگشت یک خروجی double، در اینجا از نمونهی جنریک Task استفاده شدهاست.
در ادامه برای استفاده از آن خواهیم داشت:
خروجی این متد تنها زمانی بازگشت داده میشود که نتایج leftOperand و rightOperand از وب سرویس فرضی، دریافت شده باشند و در اختیار مصرف کننده قرارگیرند. بنابراین همانطور که ملاحظه میکنید از واژهی کلیدی await جهت تشکیل یک عملیات غیرهمزمان و مدیریت سادهتر کدهای نهایی، شبیه به کدهای معمولی همزمان استفاده شدهاست.
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
اگر await را از این مثال حذف کنیم، خطای کامپایل زیر را دریافت خواهیم کرد:
خروجی متد GetSumAsync صرفا یک Task است و نه یک عدد. پس از استفاده از await، عملیات آن انجام شده و بازگشت داده میشود.
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
نکتهی مهم این کد علاوه بر ساده سازی اعمال غیر همزمان، برای استفاده از نتیجهی نهایی آن، نیازی به SynchronizationContext معرفی شده در تاریخچهی ابتدای بحث نیست. نتیجهی دریافتی از آن در ترد اصلی برنامه تحویل داده شده و به سادگی قابل استفاده است.
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
اگر این متد را اجرا کنید (در آن await بکار نرفته)، بلافاصله خروجی ذیل را مشاهده خواهید کرد:
به این معنا که در اصل، همانند سایر روشهای async موجود از دات نت یک، در اینجا نیز فراخوانی متد async ترد اصلی را بلافاصله آزاد میکند و ترد آنرا قفل نخواهد کرد. استفاده از await نیز عملکرد کدها را تغییر نمیدهد. تنها کامپایلر در پشت صحنه همان کدهای لازم جهت مدیریت روالهای رخدادگردان و callbackها را تولید میکند، به نحوی که صرفا نحوهی کدنویسی ما همزمان به نظر میرسد، اما در پشت صحنه، نحوهی اجرای آن غیرهمزمان است.
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
این روال متداول VS.NET بوده است تا به امروز. برای مثال اگر VS 2010 را نصب کنید و سپس یک برنامهی دات نت 3.5 را ایجاد کنید، امکان استفادهی کامل از تمام امکانات سیشارپ 4، مانند آرگومانهای نامدار و یا مقادیر پیش فرض آرگومانها را در یک برنامهی دات نت 3.5 نیز خواهید داشت. همین نکته در مورد async نیز صادق است. VS 2012 (یا نگارشهای جدیدتر) را نصب کنید و سپس یک پروژهی دات نت 4 را آغاز کنید. امکان استفاده از async و await را خواهید داشت. البته در این حالت دسترسی به متدهای الحاقی جدید را مانند DownloadStringTaskAsync نخواهید داشت. برای رفع این مشکل باید بستهی Microsoft.Bcl.Async را نیز توسط نیوگت نصب کنید.
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
public string TestNoneAsync() { var webClient = new WebClient(); return webClient.DownloadString("http://www.google.com"); }
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
public void TestAsync() { var webClient = new WebClient(); webClient.DownloadStringAsync(new Uri("http://www.google.com")); webClient.DownloadStringCompleted += webClientDownloadStringCompleted; } void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // use e.Result }
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
using System; using System.Linq; using System.Net; using System.Windows.Forms; namespace Async02 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnGetInfo_Click(object sender, EventArgs e) { var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); txtResults.Text = headersText; }, null); } private string formatHeaders(WebHeaderCollection headers) { var headerString = headers.Keys.Cast<string>() .Select(header => string.Format("{0}:{1}", header, headers[header])); return string.Join(Environment.NewLine, headerString.ToArray()); } } }
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
private void btnGetInfo_Click(object sender, EventArgs e) { var sync = SynchronizationContext.Current; var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); sync.Post(delegate { txtResults.Text = headersText; }, null); }, null); }
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System; using System.Threading.Tasks; namespace Async01 { public class AsyncExample { public async Task DoWorkAsync(int parameter) { await Task.Delay(parameter); Console.WriteLine(parameter); } } }
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
public async Task<double> GetNumberAsync() { var generator = new Random(); await Task.Delay(generator.Next(1000)); return generator.NextDouble(); }
در ادامه برای استفاده از آن خواهیم داشت:
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
public async Task<double> GetProductOfSumAsync() { var leftOperand = GetSumAsync(); var rightOperand = GetSumAsync(); return await leftOperand * await rightOperand; }
Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
public async Task<string> DownloadAsync() { var webClient = new WebClient(); return await webClient.DownloadStringTaskAsync("http://www.google.com"); }
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
public void TestDownloadAsync() { Debug.WriteLine("Before DownloadAsync"); DownloadAsync(); Debug.WriteLine("After DownloadAsync"); }
Before DownloadAsync After DownloadAsync
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
PM> Install-Package Microsoft.Bcl.Async
بازخوردهای دوره
تزریق وابستگیهای AutoMapper در لایه سرویس برنامه
- هر زمان که نگارش 4 آن نهایی شد، این مسایل باید بررسی شوند.
- همچنین این مسایل را هم باید با نویسندهی اصلی آن مطرح کنید؛ نه اینجا.
- همچنین این مسایل را هم باید با نویسندهی اصلی آن مطرح کنید؛ نه اینجا.