جاءت هذه الترجمة بفضل التعليق الجيد 0x1000000 .

قدم .NET Framework 4 مساحة System.Threading.Tasks ، ومعها فئة المهمة. هذا النوع والمهمة <النتيجة> التي تم إنشاؤها منه كانت تنتظر وقتًا طويلاً حتى يتم التعرف عليها بواسطة المعايير في .NET باعتبارها الجوانب الرئيسية لنموذج البرمجة غير المتزامن الذي تم تقديمه في C # 5 مع عبارات التزامن / الانتظار. في هذه المقالة ، سأتحدث عن أنواع جديدة من ValueTask / ValueTask <TResult> ، المصممة لتحسين أداء الطرق غير المتزامنة في الحالات التي يجب فيها أخذ مقدار تخصيص الذاكرة في الاعتبار.
مهمة
تعمل المهمة بأدوار مختلفة ، ولكن العنصر الرئيسي هو "الوعد" (الوعد) ، وهو كائن يمثل إمكانية إكمال بعض العمليات. يمكنك بدء عملية والحصول على كائن Task لها ، والذي سيتم تنفيذه عند اكتمال العملية ، والذي يمكن أن يحدث في الوضع المتزامن كجزء من تهيئة العملية (على سبيل المثال ، استلام البيانات الموجودة بالفعل في المخزن المؤقت) ، في وضع غير متزامن مع التنفيذ في الوقت الحالي عندما تحصل على مهمة (تلقي البيانات ليس من المخزن المؤقت ، ولكن بسرعة كبيرة) ، أو في وضع غير متزامن ، ولكن بعد أن لديك بالفعل مهمة (تلقي بيانات من مورد بعيد). نظرًا لأن العملية يمكن أن تنتهي بشكل غير متزامن ، فأنت إما تمنع تدفق التنفيذ أو تنتظر النتيجة (مما يجعل عدم التزامن للمكالمة بلا معنى) ، أو إنشاء وظيفة رد اتصال سيتم تفعيلها بعد اكتمال العملية. في .Net 4 ، يتم تطبيق إنشاء رد اتصال عن طريق أساليب ContinueWith لكائن المهمة ، والتي توضح هذا النموذج بوضوح من خلال قبول وظيفة المفوض لتشغيله بعد تنفيذ المهمة:
SomeOperationAsync().ContinueWith(task => { try { TResult result = task.Result; UseResult(result); } catch (Exception e) { HandleException(e); } });
ولكن في .NET Framework 4.5 و C # 5 ، يمكن ببساطة استدعاء كائنات المهمة بواسطة عامل انتظار ، مما يجعل من السهل الحصول على نتيجة عملية غير متزامنة ، وسوف تعمل الشفرة التي تم إنشاؤها والتي تم تحسينها للخيارات المذكورة أعلاه بشكل صحيح في جميع الحالات عند اكتمال العملية في الوضع المتزامن ، بسرعة غير متزامن أو غير متزامن مع إجراء callbacka:
TResult result = await SomeOperationAsync(); UseResult(result);
المهمة هي فئة مرنة للغاية ولديها عدد من المزايا. على سبيل المثال ، يمكنك الأداء في انتظار عدة مرات لأي عدد من المستهلكين في وقت واحد. يمكنك وضعه في مجموعة (قاموس) للانتظار المتكرر في المستقبل ، لاستخدامه كذاكرة تخزين مؤقت لنتائج المكالمات غير المتزامنة. يمكنك حظر التنفيذ أثناء انتظار المهمة لإكمالها إذا لزم الأمر. ويمكنك كتابة وتطبيق عمليات متنوعة على كائنات المهام (تسمى أحيانًا "أدوات دمج") ، على سبيل المثال ، "عند وجود" للانتظار غير المتزامن للإكمال الأول للعديد من المهام.
ولكن هذه المرونة تصبح غير ضرورية في الحالة الأكثر شيوعًا: ما عليك سوى استدعاء العملية غير المتزامنة وانتظر المهمة لإكمالها:
TResult result = await SomeOperationAsync(); UseResult(result);
هنا نحن لسنا بحاجة إلى الانتظار حتى عدة مرات. نحن لسنا بحاجة للتأكد من أن التوقعات تنافسية. نحن لسنا بحاجة لأداء قفل متزامن. لن نكتب مولدات. نحن فقط في انتظار وعد عملية غير متزامنة لإكمال. في النهاية ، هذه هي الطريقة التي نكتب بها رمز متزامن (على سبيل المثال ، نتيجة TResult = SomeOperation () ؛) ، ويتم ترجمتها عادةً إلى التزامن / انتظار.
علاوة على ذلك ، فإن Task لها نقاط ضعف محتملة ، خاصةً عند إنشاء عدد كبير من الحالات ، والإنتاجية العالية والأداء هما من المتطلبات الأساسية - المهمة عبارة عن فئة. هذا يعني أن أي عملية تحتاج إلى مهمة يتم إجبارها على إنشاء كائن ووضعه ، وكلما تم إنشاء المزيد من الكائنات ، زاد عمل مجمع أداة تجميع البيانات المهملة (GC) ، ويستهلك هذا العمل موارد يمكننا إنفاقها على شيء أكثر مفيد.
وقت التشغيل ومكتبات النظام تساعد في تخفيف هذه المشكلة في العديد من الحالات. على سبيل المثال ، إذا كتبنا طريقة مثل هذا:
public async Task WriteAsync(byte value) { if (_bufferedCount == _buffer.Length) { await FlushAsync(); } _buffer[_bufferedCount++] = value; }
كقاعدة عامة ، سيكون هناك مساحة حرة كافية في المخزن المؤقت ، وسيتم تنفيذ العملية بشكل متزامن. عندما يحدث هذا ، ليست هناك حاجة لفعل أي شيء مع المهمة ، والتي ينبغي إرجاعها ، نظرًا لعدم وجود قيمة إرجاع ، يستخدم هذا المهمة كمكافئ لطريقة متزامنة تقوم بإرجاع قيمة فارغة (خالية). لذلك ، يمكن للبيئة ببساطة تخزين مهمة غير عامة مؤقتًا واستخدامها مرارًا وتكرارًا كنتيجة للتنفيذ لأي طريقة مزامنة تنتهي بشكل متزامن (يمكن الحصول على ذاكرة التخزين المؤقت المفردة هذه من خلال Task.CompletedTask). أو ، على سبيل المثال ، تكتب:
public async Task<bool> MoveNextAsync() { if (_bufferedCount == 0) { await FillBuffer(); } return _bufferedCount > 0; }
وبوجه عام ، توقع أن تكون البيانات موجودة بالفعل في المخزن المؤقت ، لذلك تقوم الطريقة ببساطة بالتحقق من قيمة _bufferedCount ، وترى أنها أكبر من 0 ، وإرجاع true ؛ وفقط في حالة عدم وجود بيانات في المخزن المؤقت بعد ، فأنت بحاجة إلى إجراء عملية غير متزامنة. ونظرًا لوجود نتيجتين محتملتين فقط من النوع Boolean (صواب وخطأ) ، لا يوجد سوى كائنين محتملين من مهام المهام اللازمة لتمثيل هذه النتائج ، حيث يمكن للبيئة تخزين هذه الكائنات مؤقتًا وإعادتها بالقيمة المقابلة دون تخصيص ذاكرة. فقط في حالة الاكتمال غير المتزامن ، ستحتاج الطريقة إلى إنشاء مهمة جديدة ، لأنه يجب أن يتم إرجاعها قبل معرفة نتيجة العملية.
توفر البيئة التخزين المؤقت لبعض الأنواع الأخرى ، ولكن من غير الواقعي تخزين جميع الأنواع الممكنة مؤقتًا. على سبيل المثال ، الطريقة التالية:
public async Task<int> ReadNextByteAsync() { if (_bufferedCount == 0) { await FillBuffer(); } if (_bufferedCount == 0) { return -1; } _bufferedCount--; return _buffer[_position++]; }
كما سيتم تنفيذها في كثير من الأحيان بشكل متزامن. ولكن على عكس المتغير الناتج عن النوع Boolean ، تُرجع هذه الطريقة Int32 ، التي تحتوي على حوالي 4 مليارات قيمة ، وتخزين ذاكرة التخزين المؤقت لكافة المتغيرات المهمة <int> يتطلب مئات غيغا بايت من الذاكرة. توفر البيئة ذاكرة تخزين مؤقت صغيرة للمهمة <int> ، ولكن مجموعة محدودة للغاية من القيم ، على سبيل المثال ، إذا انتهت هذه الطريقة بشكل متزامن (البيانات موجودة بالفعل في المخزن المؤقت) مع قيمة الإرجاع 4 ، فستكون مهمة مخزنة مؤقتًا ، ولكن إذا تم إرجاع القيمة 42 ، فستحتاج إلى إنشاء واحدة جديدة المهمة <int> ، تشبه استدعاء Task.FromResult (42).
تحاول العديد من أساليب المكتبات تهدئة ذلك من خلال توفير ذاكرة التخزين المؤقت الخاصة بها. على سبيل المثال ، التحميل الزائد في .NET Framework 4.5 لطريقة MemoryStream.ReadAsync ينتهي دائمًا بشكل متزامن ، لأنه يقرأ البيانات من الذاكرة. إرجاع ReadAsync مهمة <int> ، حيث تشير نتيجة Int32 إلى عدد البايتات التي تمت قراءتها. غالبًا ما تستخدم هذه الطريقة في حلقة ، وغالبًا مع نفس العدد المطلوب من البايتات لكل مكالمة ، وغالبًا ما يتم تلبية هذه الحاجة بالكامل. لذلك بالنسبة للمكالمات المتكررة إلى ReadAsync ، فمن المعقول توقع أن المهمة <int> ستعود بشكل متزامن بنفس القيمة كما في المكالمة السابقة. لذلك ، ينشئ MemoryStream ذاكرة تخزين مؤقت لكائن واحد تم إرجاعه في آخر مكالمة ناجحة. وفي المكالمة التالية ، إذا تكررت النتيجة ، فسوف تُرجع الكائن المخزن مؤقتًا ، وإذا لم يكن الأمر كذلك ، فقم بإنشاء كائن جديد باستخدام Task.FromResult ، واحفظه في ذاكرة التخزين المؤقت وأعده.
ومع ذلك ، هناك العديد من الحالات الأخرى عندما يتم تنفيذ العملية بشكل متزامن ، ولكن يتم فرض الكائن Task <TResult>.
ValueTask <النتيجة> والتنفيذ المتزامن
كل هذا يتطلب تطبيق نوع جديد في .NET Core 2.0 ، والذي كان متاحًا في الإصدارات السابقة من .NET في نظام NuGet.Threading.Tasks.Extensions: ValueTask <TResult> الحزمة.
تم إنشاء ValueTask <TResult> في .NET Core 2.0 كهيكل قادر على التفاف كل من TResult والمهمة <TResult>. هذا يعني أنه يمكن إرجاعها من طريقة المزامنة ، وإذا تم تنفيذ هذه الطريقة بشكل متزامن وبنجاح ، فلن تحتاج إلى وضع أي كائن على كومة الذاكرة المؤقتة: يمكنك ببساطة تهيئة بنية ValueTask <TResult> هذه مع القيمة TResult وإعادتها. فقط في حالة التنفيذ غير المتزامن ، سيتم وضع كائن Task <TResult> ، وسوف يقوم ValueTask <TResult> بالتفافه (لتقليل حجم الهيكل وتحسين حالة التنفيذ الناجح ، فإن الطريقة غير المتزامنة ، التي تنتهي باستثناء غير مدعوم ، ستضع المهمة <TResult> أيضًا تقوم ValueTask <TResult> أيضًا بلف المهمة <النتيجة> ، ولن تحمل معها حقلًا إضافيًا لتخزين الاستثناء).
بناءً على ذلك ، يجب ألا تتعامل طريقة مثل MemoryStream.ReadAsync ، ولكن إرجاع ValueTask <int> ، مع التخزين المؤقت ، ولكن بدلاً من ذلك يمكن كتابتها على النحو التالي:
public override ValueTask<int> ReadAsync(byte[] buffer, int offset, int count) { try { int bytesRead = Read(buffer, offset, count); return new ValueTask<int>(bytesRead); } catch (Exception e) { return new ValueTask<int>(Task.FromException<int>(e)); } }
ValueTask <النتيجة> والتنفيذ غير المتزامن
القدرة على كتابة طريقة غير متزامنة يمكن أن تكتمل بشكل متزامن دون الحاجة إلى وضع إضافي للنتيجة هي فوز كبير. لهذا السبب تم إضافة ValueTask <TResult> في .NET Core 2.0 ، ويتم الآن الإعلان عن أساليب جديدة من المحتمل استخدامها في التطبيقات التي تتطلب أداء مع إرجاع ValueTask <TResult> بدلاً من Task <TResult>. على سبيل المثال ، عندما أضفنا حملًا جديدًا ReadAsync من فئة Stream إلى .NET Core 2.1 ، لكي نتمكن من تمرير الذاكرة بدلاً من البايت [] ، نرجع نوع ValueTask <int> فيه. في هذا النموذج ، يمكن استخدام كائنات Stream (التي يتم فيها تنفيذ الأسلوب ReadAsync في الغالب بشكل متزامن ، كما في المثال السابق لـ MemoryStream) مع تخصيص ذاكرة أقل بكثير.
ومع ذلك ، عندما نعمل مع خدمات ذات نطاق ترددي مرتفع للغاية ، ما زلنا نريد تجنب تخصيص الذاكرة قدر الإمكان ، مما يعني تقليل وإلغاء تخصيص الذاكرة على طول مسار التنفيذ غير المتزامن.
في نموذج انتظار ، لأي عملية تكتمل بشكل غير متزامن ، نحتاج إلى القدرة على إرجاع كائن يمثل الإكمال المحتمل للعملية: يحتاج المتصل إلى إعادة توجيه رد الاتصال الذي سيبدأ في نهاية العملية ، وهذا يتطلب كائنًا فريدًا في الكومة ، والذي يمكن أن يكون بمثابة قناة نقل ل هذه العملية خاصة. هذا ، في الوقت نفسه ، لا يعني أي شيء ما إذا كان سيتم إعادة استخدام هذا الكائن بعد اكتمال العملية. إذا كان من الممكن إعادة استخدام هذا الكائن ، فيمكن لـ API تنظيم ذاكرة تخزين مؤقت لواحد أو أكثر من هذه الكائنات ، واستخدامه في عمليات متسلسلة ، بمعنى عدم استخدام نفس الكائن لعدة عمليات غير متزامنة وسيطة ، ولكن للاستخدام للوصول غير التنافسي.
في .NET Core 2.1 ، تم تحسين فئة ValueTask <TResult> لدعم التجميع وإعادة الاستخدام المتماثلين. بدلاً من مجرد التفاف TResult أو Task <TResult> ، يمكن لفئة تمت مراجعتها التفاف واجهة IValueTaskSource <TResult> جديدة. توفر هذه الواجهة الوظيفة الأساسية اللازمة لمرافقة عملية غير متزامنة مع كائن <Tesult> ValueTask بنفس الطريقة التي تعمل بها المهمة <TResult>:
public interface IValueTaskSource<out TResult> { ValueTaskSourceStatus GetStatus(short token); void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); TResult GetResult(short token); }
يتم استخدام الأسلوب GetStatus لتطبيق خصائص مثل ValueTask <TResult> .IsCompleted ، والتي تُرجع معلومات عما إذا كانت عملية غير متزامنة قد تم تنفيذها أو إكمالها ، وكيف تم إكمالها (ناجحة أم لا). يتم استخدام الأسلوب OnCompleted بواسطة كائن انتظار لإرفاق رد اتصال لمتابعة التنفيذ من نقطة انتظار عند اكتمال العملية. وهناك حاجة إلى طريقة GetResult للحصول على نتيجة العملية ، لذلك بعد نهاية العملية ، يمكن للمتصل الحصول على كائن TResult أو تمرير أي استثناء تم طرحه.
لا يحتاج معظم المطورين إلى هذه الواجهة: فالطرق ترجع ببساطة كائن <Tesult> ValueTask ، والذي يمكن إنشاؤه كملف لكائن يقوم بتنفيذ هذه الواجهة ، وستظل طريقة الاستدعاء في الظلام. هذه الواجهة للمطورين الذين يحتاجون إلى تجنب تخصيص الذاكرة عند استخدام واجهة برمجة تطبيقات مهمة للأداء.
هناك العديد من الأمثلة لمثل هذه API في .NET Core 2.1. الطرق الأكثر شهرة هي Socket.ReceiveAsync و Socket.SendAsync مع زيادة الأحمال الجديدة المضافة في 2.1 ، على سبيل المثال
public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default);
هذا التحميل الزائد بإرجاع ValueTask <int>. إذا اكتملت العملية بشكل متزامن ، فيمكنها ببساطة إرجاع ValueTask <int> بالقيمة المقابلة:
int result = …; return new ValueTask<int>(result);
عند الإنهاء غير المتزامن ، يمكنه استخدام كائن من التجمع يقوم بتنفيذ الواجهة:
IValueTaskSource<int> vts = …; return new ValueTask<int>(vts);
يدعم تطبيق Socket كائنًا واحدًا في التجمع للاستقبال والآخر للإرسال ، حيث لا يمكن أن يكون هناك أكثر من كائن واحد لكل اتجاه في انتظار التنفيذ في وقت واحد. هذه الأحمال الزائدة لا تخصص الذاكرة ، حتى في حالة وجود عملية غير متزامنة. هذا السلوك هو واضح أكثر في فئة NetworkStream.
على سبيل المثال ، في .NET Core 2.1 Stream يوفر:
public virtual ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken);
الذي تم إعادة تعريفه في NetworkStream. يستخدم أسلوب NetworkStream.ReadAsync ببساطة طريقة Socket.ReceiveAsync ، بحيث يتم بث الأرباح في Socket إلى NetworkStream ، ولا يخصص NetworkStream.ReadAsync الذاكرة بالفعل.
Unseded ValueTask
عندما ظهر ValueTask <TResult> في .NET Core 2.0 ، تم تحسين حالة التنفيذ المتزامن فقط من أجل استبعاد موضع كائن المهمة <TResult> إذا كانت قيمة TResult جاهزة بالفعل. هذا يعني أن فئة ValueTask غير العامة ليست ضرورية: بالنسبة لحالة التنفيذ المتزامن ، يمكن ببساطة إرجاع Task.CompletedTask المنفردة من الطريقة ، وقد تم ذلك بواسطة البيئة بشكل ضمني في أساليب المتزامن إرجاع المهام.
ومع ذلك ، مع الحصول على عمليات غير متزامنة دون تخصيص ذاكرة ، أصبح استخدام ValueTask غير المشترك مرة أخرى ذا صلة. في .NET Core 2.1 ، قدمنا ValueTask و IValueTaskSource العامة. أنها توفر مكافئات مباشرة للإصدارات العامة ، للاستخدام المماثل ، مع قيمة إرجاع فارغة فقط.
تطبيق IValueTaskSource / IValueTaskSource <T>
يجب على معظم المطورين عدم تطبيق هذه الواجهات. علاوة على ذلك ، الأمر ليس بهذه السهولة. إذا قررت القيام بذلك ، يمكن أن تعمل العديد من التطبيقات في .NET Core 2.1 كنقطة انطلاق ، على سبيل المثال:
- AwaitableSocketAsyncEventArgs
- AsyncOperation <النتيجة>
- DefaultPipeReader
لتسهيل هذا الأمر ، في .NET Core 3.0 ، نخطط لإدخال كل المنطق الضروري المضمّن في النوع ManualResetValueTaskSourceCore <TResult> ، وهي بنية يمكن تضمينها في كائن آخر يقوم بتنفيذ IValueTaskSource <TResult> و / أو IValueTaskSource ، بحيث يمكن تفويضه إلى هذا الهيكل هو الجزء الأكبر من الوظيفة. يمكنك معرفة المزيد حول هذا الأمر من https://github.com/dotnet/corefx/issues/32664 في مستودع dotnet / corefx.
أنماط تطبيق ValueTasks
للوهلة الأولى ، يكون نطاق ValueTask و ValueTask <TResult> محدودًا أكثر من Task and Task <TResult>. هذا أمر جيد ، وحتى متوقع ، لأن الطريقة الرئيسية لاستخدامهم هي ببساطة استخدام عامل التشغيل الذي ينتظر.
ومع ذلك ، نظرًا لأنه يمكنهم التفاف الكائنات التي يتم إعادة استخدامها ، فهناك قيود كبيرة على استخدامها مقارنة بالمهمة والمهمة <النتيجة> ، إذا كنت تنحرف عن الطريقة المعتادة في الانتظار البسيط. في الحالات العامة ، لا ينبغي أبدًا تنفيذ العمليات التالية باستخدام ValueTask / ValueTask <النتيجة>:
- الانتظار المتكرر ValueTask / ValueTask <النتيجة> قد يتم بالفعل التخلص من كائن النتيجة واستخدامه في عملية أخرى. في المقابل ، لا تنتقل المهمة / المهمة <TResult> أبدًا من حالة مكتملة إلى حالة غير كاملة ، بحيث يمكنك إعادة توقعها عدة مرات حسب الحاجة والحصول على نفس النتيجة في كل مرة.
- انتظار متوازي ValueTask / ValueTask <TResult> يتوقع كائن النتيجة معالجة مع رد اتصال واحد فقط من مستهلك واحد في كل مرة ، ومحاولة الانتظار من تدفقات مختلفة في نفس الوقت يمكن أن يؤدي بسهولة إلى سباقات وأخطاء برنامج خفية. بالإضافة إلى ذلك ، إنها أيضًا حالة أكثر تحديداً لعملية "إعادة الانتظار" غير الصالحة السابقة. في المقارنة ، توفر المهمة / المهمة <النتيجة> أي عدد من الانتظار المتوازي.
- باستخدام .GetAwaiter (). GetResult () عندما لم تكتمل العملية بعد. لا يحتاج تطبيق IValueTaskSource / IValueTaskSource <TResult> إلى دعم القفل حتى تكتمل العملية ، وعلى الأرجح لن تفعل ذلك ، وبالتالي فإن هذه العملية ستؤدي بالتأكيد إلى السباق وربما لن تنفذ كما تتوقع طريقة الاتصال. المهمة / المهمة <النتيجة> بحظر مؤشر الترابط الاستدعاء حتى تكتمل المهمة.
إذا تلقيت ValueTask أو ValueTask <TResult> ، لكنك تحتاج إلى تنفيذ إحدى هذه العمليات الثلاث ، فيمكنك استخدام .AsTask () ، والحصول على Task / Task <TResult> ، ثم العمل مع الكائن المستلم. بعد ذلك ، لم يعد بإمكانك استخدام ValueTask / ValueTask <TResult>.
باختصار ، القاعدة هي: عند استخدام ValueTask / ValueTask <النتيجة> يجب عليك إما أن تنتظرها مباشرة (ربما باستخدام .ConfigureAwait (false)) أو الاتصال بـ AsTask () وعدم استخدامها بعد الآن:
// , ValueTask<int> public ValueTask<int\> SomeValueTaskReturningMethodAsync(); ... // GOOD int result = await SomeValueTaskReturningMethodAsync(); // GOOD int result = await SomeValueTaskReturningMethodAsync().ConfigureAwait(false); // GOOD Task<int> t = SomeValueTaskReturningMethodAsync().AsTask(); // WARNING ValueTask<int> vt = SomeValueTaskReturningMethodAsync(); // , // // BAD: await ValueTask<int> vt = SomeValueTaskReturningMethodAsync(); int result = await vt; int result2 = await vt; // BAD: await ( ) ValueTask<int> vt = SomeValueTaskReturningMethodAsync(); Task.Run(async () => await vt); Task.Run(async () => await vt); // BAD: GetAwaiter().GetResult(), ValueTask<int> vt = SomeValueTaskReturningMethodAsync(); int result = vt.GetAwaiter().GetResult();
هناك نمط متقدم آخر يمكن للمبرمجين تطبيقه ، كما آمل ، فقط بعد القياس الدقيق والمزايا الهامة. تحتوي فئات ValueTask / ValueTask <TResult> على العديد من الخصائص التي تبلغ عن الحالة الحالية للعملية ، على سبيل المثال ، تقوم خاصية IsCompleted بإرجاع true إذا كانت العملية مكتملة (أي ، لم تعد تعمل بنجاح أو لم تنجح بنجاح) ، وإرجاع الخاصية IsCompletedSuccessfully true ، فقط إذا تم إكمالها بنجاح (أثناء انتظار النتيجة وتلقيها ، فإنها لم ترِ استثناءً). للحصول على مؤشرات الترابط الأكثر تطلبًا للتنفيذ ، حيث يرغب المطور في تجنب التكاليف التي تنشأ في الوضع غير المتزامن ، يمكن التحقق من هذه الخصائص قبل أن تقوم عملية فعلية بتدمير كائن ValueTask / ValueTask <TResult> ، على سبيل المثال ، await ، .AsTask (). على سبيل المثال ، في تطبيق SocketsHttpHandler في .NET Core 2.1 ، يقرأ الرمز من الاتصال ويتلقى ValueTask <int>. إذا تم تنفيذ هذه العملية بشكل متزامن ، فلا داعي للقلق بشأن الإنهاء المبكر للعملية. ولكن إذا تم تشغيله بشكل غير متزامن ، فيجب علينا ربط معالجة المقاطعة بحيث يقطع طلب المقاطعة الاتصال. نظرًا لأن هذا جزء من التعليمات البرمجية المجهدة للغاية ، إذا أظهر التوصيف الحاجة إلى التغيير البسيط التالي ، فيمكن تنظيمه مثل هذا:
int bytesRead; { ValueTask<int> readTask = _connection.ReadAsync(buffer); if (readTask.IsCompletedSuccessfully) { bytesRead = readTask.Result; } else { using (_connection.RegisterCancellation()) { bytesRead = await readTask; } } }
هل يجب على كل طريقة API غير متزامنة جديدة إرجاع ValueTask / ValueTask <TResult>؟
للإجابة لفترة وجيزة: لا ، بشكل افتراضي ، لا يزال الأمر يستحق اختيار المهمة / المهمة <النتيجة>.
كما هو موضح أعلاه ، فإن المهام والمهام <TResult> أسهل في الاستخدام بشكل صحيح من ValueTask و ValueTask <TResult> ، وطالما أن متطلبات الأداء لا تفوق متطلبات التطبيق العملي ، فإن المهام والمهام <TResult> هي المفضلة. بالإضافة إلى ذلك ، هناك تكاليف صغيرة مرتبطة بإرجاع ValueTask <TResult> بدلاً من Task <TResult> ، أي ، تظهر المقاييس الدقيقة أن انتظار المهمة <TResult> أسرع من انتظار ValueTask <TResult>. لذا ، إذا كنت تستخدم التخزين المؤقت للمهام ، على سبيل المثال ، تُرجع الطريقة "مهمة" أو "مهمة" ، حيث إن الأداء يستحق الأمر مع المهمة أو المهمة. تشغل كائنات ValueTask / ValueTask <TResult> عدة كلمات في الذاكرة ، لذلك ، عندما يكون متوقعًا وحقولها محجوزة في جهاز الحالة الذي يستدعي الأسلوب غير المتزامن ، فإنها ستشغل المزيد من الذاكرة فيه.
- ValueTask/ValueTask<TResult> : ) , await, ) , ) , . , / .
ValueTask ValueTask<TResult>?
.NET , Task/Task<TResult>, , ValueTask/ValueTask<TResult>, , . – IAsyncEnumerator<T>, .NET Core 3.0. IEnumerator<T> MoveNext, bool, IAsyncEnumerator<T> MoveNextAsync. , , Task, . , , , ( ), await foreach, ValueTask. , . C# , , , .