| کانال توسعه‌دهندگان سی‌شارپ |
1.03K subscribers
19 photos
3 videos
21 links
⭕️ کانال توسعه‌دهندگان سی‌شارپ دولوپیکس

💠 دولوپیکس | جامعه توسعه‌دهندگان ایرانی

💎 @Developix
🚀 Developix.ir

📌 پشتیبانی و تبلیغات:
@DevelopixSupport
Download Telegram
تفاوت <Task <T و <ValueTask<T

همونطور که میدونید هر عملیات Asynchronous نیازمند یک return type از نوع Task هست، اما یک نوع دیگه هم به اسم ValueTask وجود داره که احتمالا قبلا دیدید.
این ابجکت درواقع یک Discriminated Union هست که به زبان ساده، میتونه چندین type مختلف از دیتا رو در خودش نگه داری کنه، که توی زبان هایی مثل Typescript, Rust, FSharp و ... وجود داره، البته کار ValueTask نگه داری چند type مختلف برای شما نیست! اما چرا این type وجود داره ؟
این تابع رو تصور کنید:

async Task<int> GetFollowers(string? username)
{
    if (string.IsNullOrEmpty(username))
        return -1;

    if (!_validateRegex.IsMatch(username))
        return -2;
   
    return await GetInstagramFollowers(username);
}
زیاد پیچیده نیست، صرفا یک username به عنوان ورودی میگیره و تعداد فالور های اون کاربر رو برمیگردونه؛
اما کد ما چه مسیر هایی رو میتونه طی کنه ؟
خب ممکنه در مرحله اول ورودی ما خالی یا null باشه که با شرط اول کد تموم میشه؛
ممکنه خالی نباشه اما یک username معتبر نباشه که توی شرط دوم برسی میشه و کد دوباره تموم میشه

و حالت خوشحال کننده که username معتبر هست و فالور های اون به کاربر نشون داده میشه؛

- خب، اینا چه ربطی به ValueTask داشت ؟

ربطی نداشت واقعا، اما پیش زمینه بود؛ چند تا از این مسیر ها ، به کد asynchronous ختم میشه ؟ فقط یکیشون!
همون مسیر خوشحال کننده که درنهایت یک درخواست احتمالا به API یا DB فرستاده میشه، اما ما برای هر سه مسیر داریم یک Task برمیگردونیم!

- خب، مشکلش چیه؟

هیچ مشکلی نداره، مشکل جایی به وجود میاد که این متد صد ها هزار بار یا بیشتر صدا زده بشه، برای هر بار اجرای این متود، شما یک ابجکت اضافه، جدای از تعداد فالور های کاربر ذخیره میکنید، بله خود Task که از آسمون نازل نمیشه، اون هم داخل Heap ذخیره میشه درست مثل بقیه Object ها، اون هم درصورتی که اینجا ما فقط توی یک مسیر ازش استفاده کردیم، درواقع دو مسیر دیگه، منابع اضافه مصرف میکنن!!
و خب اینکار در دنیای ما، جنایت به حساب میاد؛ اما اینجاست که ValueTask به دردمون میخوره، اگه تابع شما به جای Task ، صرفا ValueTask برگردونه، در دو مسیر دیگه که کد به درخواست asynchronous منتهی نمیشن، صرفا مقدار خالص رو به شما میده، و در مسیر اخری که کد ما به await کردن و درخواست I/O میرسه، یک Task به شما برمیگردونه.

درواقع در لایه زیرین شما همچین چیزی دارید:
ValueTask<Task<T>,T>
که یا میتونه T باشه یا <Task <T

اگر واقعا Task اتون به خطوط async برسه، یک Task دارید، و اگر صرفا در مراحل قبل از اون به اتمام برسه، یک T به صورت مستقیم دارید:
async ValueTask<int> GetFollowersAsync(string? username)
{
    if (string.IsNullOrEmpty(username))
        return -1; //int

    if (!_validateRegex.IsMatch(username))
        return -2; //int
   
    return await GetInstagramFollowers(username); //Task<int>
}
درنتیجه، شما در دو مسیر، از مصرف بیخود منابع جلوگیری میکنید، که توی تعداد درخواست های بالا میتونه زندگیتون رو نجات بده؛
کاربرد های مختلفی برای ValueTask وجود داره، از معروف هاش میشه به سیستم caching اشاره کرد، اما صرفا شرط ساده ای که باید به خاطر بسپارید:

"اگر کد، چندین مسیر مختلف داره و بعضی از اون مسیر ها میتونن به کد async یا sync منتهی بشن، ValueTask میتونه مورد خوبی برای بهینه کردن کد باشه"

و البته محدودیت هایی هم برای استفادش وجود داره:

-برخلاف Task ها، ValueTask ها نباید بیشتر از 1 بار await بشن!
-بیشتر از 1 بار نباید از AsTask بر روی ValueTask استفاده کنید!
-استفاده از Result. یا ()GetAwaiter().GetResult زمانی که هنوز عملیات به اتمام نرسیده یا بیشتر از یک بار استفاده بشن( کلا از اینها هیچوقت استفاده نکنید، چه Task و چه ValueTask )

#performance #async #task

👤 QWxp

💎 Channel: @DevelopixCSharp
👍14👎31
استفاده حرفه‌ای از ConfigureAwait(false) در async/await

خیلی وقت‌ها توی کتابخونه‌ها و لایه‌های غیر UI، از async/await استفاده می‌شه ولی ناخواسته Performance و Scale خراب می‌شه؛ فقط به خاطر برنگشتنِ درست به Thread قدیمی.

نکتهٔ مهم اینه: در کدهای غیر UI (مثل لایهٔ Data Access، Service، Library) معمولاً نیازی نیست بعد از await برگردیم به همون SynchronizationContext قبلی. اینجاست که 🧠 ConfigureAwait(false) کمک می‌کنه.

ایده:
هرجا در Library Code هستیم و بعد از await به Context قبلی نیاز نداریم، از ConfigureAwait(false) استفاده کنیم تا:
- Deadlockهای کلاسیک در ASP.NET قدیمی کمتر بشه
- Performance بهتر بشه چون نیاز به Capture/Restore Context نیست

مثال ساده در یک Service غیر UI:
public async Task<UserDto> GetUserAsync(int id)
{
// فرض: این متد توی لایه Service یا Repository صدا زده می‌شه
using var client = new HttpClient();

var response = await client
.GetAsync($"https://api.example.com/users/{id}")
.ConfigureAwait(false);

response.EnsureSuccessStatusCode();

var json = await response.Content
.ReadAsStringAsync()
.ConfigureAwait(false);

return JsonSerializer.Deserialize<UserDto>(json)!;
}


📌 نکات مهم:
- توی UI (WPF, WinForms, MAUI) یا Razor Pages که بعد از await می‌خوای کنترل‌های UI رو آپدیت کنی، از ConfigureAwait(false) استفاده نکن چون به Thread UI برنمی‌گردی.
- در ASP.NET Core به‌خاطر نداشتن SynchronizationContext کلاسیک، مشکل Deadlock خیلی کمتره؛ ولی هنوز هم برای Libraryهای عمومی، استفاده از ConfigureAwait(false) یک Practice خوب حساب می‌شه.
- سعی کن این الگو رو توی کل لایه‌های Library یک‌دست نگه داری تا رفتار کد قابل‌پیش‌بینی بمونه.

برای توضیح رسمی مایکروسافت دربارهٔ async/await و Context:
مستندات async در C# (Microsoft Docs)

امتحان این روش توی Serviceها و Libraryها، مخصوصاً زیر لود بالا، خیلی زود توی لاگ‌ها و Performance خودش رو نشون می‌ده 🚀

🔖 #CSharp #سی_شارپ #CSharp #async #ConfigureAwait #Performance #_NET

👤 Developix

💎 Channel: @DevelopixCSharp
🔥3
خیلی از Memory Leakهای ریز تو پروژه‌های #CSharp از یه چیز ساده شروع می‌شن: فراموش‌کردن Dispose روی resourceهایی مثل HttpClient، SqlConnection، فایل‌ها و… 😅

تو دات‌نت جدید، using راحت‌تر و خواناتر شده و اگر درست استفاده بشه، هم کد تمیزتر می‌شه هم از نشت حافظه و handleها جلوگیری می‌کنه. 👇

۱️⃣ الگوی قدیمی using بلاکی
using (var stream = File.OpenRead("data.json"))
using (var reader = new StreamReader(stream))
{
var content = reader.ReadToEnd();
Console.WriteLine(content);
}

اینجا به‌محض خروج از بلاک، هر دو object به‌صورت خودکار Dispose می‌شن.

۲️⃣ الگوی جدید using declaration (از C# 8 به بعد)
برای کاهش nesting و خوانایی بیشتر:
using var stream = File.OpenRead("data.json");
using var reader = new StreamReader(stream);

var content = reader.ReadToEnd();
Console.WriteLine(content);
// در انتهای متد، خودشون Dispose می‌شن

تو این حالت، scopeِ using var کل متده؛ یعنی پایان متد = زمان Dispose. این الگو توی متدهای کوتاه و تمیز عالی جواب می‌ده و از بلاک‌های تو در تو کم می‌کنه. 💡

۳️⃣ نکته مهم در متدهای طولانی
اگر متد خیلی طولانیه و resource فقط تو یه بخش کوچیک لازم می‌شه، بهتره همون الگوی بلاکی رو نگه داری تا resource زودتر آزاد بشه و تا آخر متد تو حافظه نمونه.

۴️⃣ async و IAsyncDisposable
برای کلاس‌هایی که IAsyncDisposable رو پیاده‌سازی می‌کنن (مثلاً بعضی clientهای شبکه):
await using var client = new SomeAsyncClient();
await client.DoWorkAsync();

اینجا در انتهای scope، به‌جای Dispose، متد DisposeAsync صدا زده می‌شه و resourceها به‌شکل async آزاد می‌شن.

جمع‌بندی: استفاده درست از using (به‌خصوص using var و await using) یه راه خیلی ساده و کاربردی برای جلوگیری از Memory Leak و تمیز نگه‌داشتن کده. چند جای پروژه رو که resource سنگین دارن چک و این الگو رو اعمال کن؛ تأثیرش تو performance و پایداری سرویس کاملاً قابل لمسه. 🚀

🔗 مرجع رسمی Microsoft Docs:
https://learn.microsoft.com/dotnet/csharp/language-reference/statements/using

🔖 #CSharp #سی_شارپ #C# #using #dispose #memory #performance #_NET

👤 Developix

💎 Channel: @DevelopixCSharp
1