ValueTask وتجنّب التخصيصات
استخدموا ValueTask لتقليل تخصيصات heap في المسارات الساخنة، وافهموا قيوده، وتجنّبوا أخطاء الاستخدام الشائعة.
ValueTask وتجنّب التخصيصات درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
مشكلة التخصيص مع Task
يُعد Task<T> نوعًا مرجعيًا — إذ يخصص كل أسلوب async كائن Task في الكومة، حتى إذا كانت النتيجة متاحة بشكل متزامن (مثلًا من ذاكرة التخزين المؤقت). وقد يؤدي ذلك إلى ضغط كبير على جامع البيانات المهملة (GC) في المسارات الساخنة. ويحل ValueTask<T> هذه المشكلة.
ما هو ValueTask؟
ValueTask<T> هو بنية يمكن أن تمثل إما نتيجة متاحة بشكل متزامن أو عملية غير متزامنة معلّقة. وعندما تكون النتيجة متزامنة، لا تكون هناك حاجة إلى تخصيص في الكومة.
// Task<T>: always allocates, even when result is cached
public async Task<string> GetCached_Task() =>
_cache.TryGetValue("key", out var v) ? v : await FetchAsync();
// ValueTask<T>: zero allocation when value is cached
public ValueTask<string> GetCached_ValueTask() =>
_cache.TryGetValue("key", out var v)
? ValueTask.FromResult(v) // no allocation
: new ValueTask<string>(FetchAsync()); // allocation only when neededإعادة ValueTask من واجهة
استخدم ValueTask<T> في أساليب الواجهات التي تُستدعى كثيرًا والتي تكتمل غالبًا بشكل متزامن. ويستخدمه المستهلكون تمامًا كما يستخدمون Task<T>.
public interface IProductRepository
{
// ValueTask: often completes synchronously from cache
ValueTask<Product?> GetByIdAsync(int id);
// Task: always async (DB query)
Task<List<Product>> SearchAsync(string query);
}
// Consumption is identical to Task:
var product = await _repo.GetByIdAsync(42);
var results = await _repo.SearchAsync("keyboard");ValueTask مع IValueTaskSource
لتحقيق أقصى أداء، طبّق IValueTaskSource<T> لتجميع كائن الحالة الداعم في تجمع. يؤدي ذلك إلى تجنب جميع عمليات التخصيص حتى عند اكتمال العمليات غير المتزامنة — ويُستخدم داخليًا في كود المقابس والإدخال والإخراج في .NET.
// In most cases, use the simple ValueTask approach.
// IValueTaskSource is for library authors needing zero-alloc async at extreme scale.
// Example: System.IO.Pipelines uses this internally
var result = await pipe.Reader.ReadAsync(ct);
// ReadAsync returns ValueTask<ReadResult> backed by IValueTaskSource
// -- zero allocation for every read even under high throughputقيود ValueTask: الانتظار مرة واحدة فقط
قاعدة مهمة: يجب انتظار ValueTask<T> مرة واحدة كحد أقصى، ولا يمكنك استدعاء .Result قبل اكتماله. يؤدي انتهاك ذلك إلى سلوك غير معرّف.
var vt = repo.GetByIdAsync(42);
// BAD: await multiple times
var a = await vt; // OK
var b = await vt; // UNDEFINED BEHAVIOR — don't do this!
// BAD: two concurrent awaits
var t1 = vt.AsTask(); // converts to Task (safe to share)
var t2 = vt.AsTask(); // second AsTask() on same ValueTask — WRONG
// GOOD: if you need multiple awaits, convert to Task
var task = repo.GetByIdAsync(42).AsTask();
var r1 = await task;
var r2 = await task; // OK — Task can be awaited multiple timesدليل الاختيار بين ValueTask<T> وTask<T>
استخدم ValueTask<T> فقط عندما يُظهر تحليل الأداء وجود ضغط ناتج عن التخصيص في الأساليب غير المتزامنة. لا تستخدمه في كل مكان — فهو يزيد التعقيد دون فائدة في الكود منخفض التكرار.
// USE ValueTask when:
// 1. The method often completes synchronously (cache hit)
// 2. The method is on a hot path called millions of times/sec
// 3. You're writing a library with high-throughput I/O
// USE Task when:
// 1. The result is always async
// 2. The method might be awaited multiple times
// 3. You use Task.WhenAll / Task.WhenAny
// 4. You store the task in a variable and share itValueTask غير العام
يعمل ValueTask (غير العام) مع الأساليب التي تعيد Task (أساليب غير متزامنة لا تعيد قيمة). استخدمه للأساليب التي تُستدعى كثيرًا والتي تكتمل غالبًا بشكل متزامن.
public interface ICache
{
// Often a no-op when value exists — ValueTask avoids allocation
ValueTask SetAsync(string key, string value);
ValueTask InvalidateAsync(string key);
}
public class InMemoryCache : ICache
{
private readonly Dictionary<string, string> _data = new();
public ValueTask SetAsync(string key, string value)
{
_data[key] = value;
return ValueTask.CompletedTask; // no allocation
}
}قياس التخصيص باستخدام BenchmarkDotNet
استخدم [MemoryDiagnoser] في BenchmarkDotNet لقياس التخصيصات لكل عملية. وهذه هي الطريقة الموثوقة الوحيدة للتحقق من أن ValueTask يقدم فائدة فعلية.
[MemoryDiagnoser]
public class CacheBenchmark
{
private readonly CachedRepo _repo = new();
[Benchmark]
public async Task<Product?> Task_GetById()
=> await _repo.GetByIdTask(1);
[Benchmark]
public async ValueTask<Product?> ValueTask_GetById()
=> await _repo.GetByIdValueTask(1);
}
// Run: dotnet run -c Release
// Compare Gen0 allocations column — ValueTask should show 0B on cache hitValueTask في SocketsHttpHandler
تستخدم حزمة HTTP في .NET ValueTask على نطاق واسع داخليًا. يساعدك فهم ذلك على كتابة عملاء HTTP عالية الأداء تتوافق مع استراتيجية التخصيص في بيئة التشغيل.
// HttpClient uses ValueTask-based sockets internally.
// When using HttpClient.GetAsync(), the response is awaited with Task<HttpResponseMessage>.
// For streaming large bodies, use:
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
// Stream reads internally use ValueTask<int> for zero-allocation I/Oمن الواقع العملي: خدمة ذاكرة تخزين مؤقت لمسار ساخن
خدمة تخزين مؤقت قد تكون كل مكالمة فيها إصابة لذاكرة التخزين المؤقت — يزيل ValueTask التخصيصات من المسار الساخن مع الاستمرار في دعم الرجوع غير المتزامن عند فشل الإصابة.
public class ProductCache
{
private readonly Dictionary<int, Product> _cache = new();
private readonly IProductRepository _repo;
public ProductCache(IProductRepository repo) => _repo = repo;
public ValueTask<Product?> GetAsync(int id)
{
// Cache hit: no allocation
if (_cache.TryGetValue(id, out var cached))
return ValueTask.FromResult<Product?>(cached);
// Cache miss: async fetch
return new ValueTask<Product?>(FetchAndCacheAsync(id));
}
private async Task<Product?> FetchAndCacheAsync(int id)
{
var product = await _repo.GetByIdAsync(id);
if (product is not null) _cache[id] = product;
return product;
}
}اختبار سريع
ما القاعدة المهمة المتعلقة بانتظار ValueTask
مراجعة: ValueTask وتجنب التخصيصات
أهم النقاط المستخلصة:
ValueTask<T>: بنية تتجنب التخصيص في الكومة عندما تكون النتيجة متزامنة- مناسب للمسارات الساخنة التي تكتمل غالبًا بشكل متزامن (مثل إصابات ذاكرة التخزين المؤقت)
- لا تنتظر
ValueTask<T>أكثر من مرة مطلقًا — استخدم.AsTask()لمشاركته - استخدم
[MemoryDiagnoser]للتحقق من وفورات التخصيص قبل إجراء التحسين - فضّل
Task<T>للاستخدام العام؛ واستخدمValueTask<T>فقط عندما يبرر تحليل الأداء ذلك
الأسئلة الشائعة
هل درس «ValueTask وتجنّب التخصيصات» مجاني؟
نعم — نص درس «ValueTask وتجنّب التخصيصات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «ValueTask وتجنّب التخصيصات»؟
استخدموا ValueTask لتقليل تخصيصات heap في المسارات الساخنة، وافهموا قيوده، وتجنّبوا أخطاء الاستخدام الشائعة. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «ValueTask وتجنّب التخصيصات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- IAsyncEnumerable وawait foreach
- System.Threading.Channels
- ValueTask وتجنّب التخصيصات
- ConfigureAwait وسياق المزامنة