هل من الممكن في .NET ، باستخدام C#، تحقيق النمط غير المتزامن القائم على الحدث دون تعدد الترقيم؟
-
27-09-2019 - |
المحلول
أعتقد أن كل BeginXyz
تقوم العمليات التي تنفذ نموذج البرمجة غير المتزامن القياسي بتشغيل رد الاتصال على مؤشر ترابط مؤشرات الترابط ، مما يجعل التطبيق متعدد الخيوط تلقائيًا.
ومع ذلك ، يمكنك تحقيق نموذج برمجة غير متزامن أحادي الخيوط من خلال مزامنة جميع العمليات من خلال مؤشر ترابط واجهة المستخدم الرسومية الواحدة التي يتم الحفاظ عليها لتطبيقات Windows باستخدام Control.Invoke
أو بشكل عام ، SynchronizationContext
.
كل دعوة إلى BeginXyz
يجب إعادة كتابتها على هذا المنوال:
// Start asynchronous operation here (1)
var originalContext = SynchronizationContext.Current;
obj.BeginFoo(ar =>
// Switch to the original thread
originalContext.Post(ignored => {
var res = obj.EndFoo();
// Continue here (2)
}));
سيستمر الكود الذي تم وضع علامة عليه على أنه (2) على التشغيل على نفس مؤشر الترابط مثل الرمز في (1) ، لذلك ستستخدم مؤشر ترابط مؤشر الترابط فقط لإعادة توجيه Postback إلى مؤشر الترابط الأصلي (المفرد).
كملاحظة جانبية ، يتم دعم هذا بشكل مباشر بشكل مباشر من خلال سير العمل غير المتزامن في F# ويمكن استخدامه لأسلوب أنيق للغاية من برمجة واجهة المستخدم الرسومية كما هو موضح هنا. لا أعرف node.js
, ، لكنني أفترض أنك قد تندهش أيضًا من F# سير العمل غير المتزامن لأنها رائعة حقًا بالنسبة للبرمجة غير المتزامنة/الحدث/...
نصائح أخرى
أنا أعمل على شيء من هذا القبيل في .NET كمشروع للحيوانات الأليفة. اسميها ALE (حدث حلقة آخر)... لأن البيرة.
إنه الى ابعد حد ألفا في الوقت الحالي ، ولكن كل هذا هو البيرة ، لأنني ، مثلك ، أردت أن أعرف ما إذا كان يمكن القيام بذلك.
- إنها بنية حلقة الحدث.
- إنه يعزز مكالمات I/O غير المحصورة .NET
- يستخدم مكالمات على غرار رد الاتصال لمساعدة المطورين على كتابة رمز ASYNC الذي يمكن قراءة أكثر قليلاً.
- تنفيذ مآخذ الويب Async
- تنفيذ خادم HTTP ASYNC
- عميل SQL Async SQL
على عكس بعض المحاولات الأخرى في هذا ، رأيت أنه ليس مجرد خادم ويب مع حلقة حدث. يمكنك كتابة أي نوع من التطبيقات القائمة على حلقة الأحداث خارج هذا.
مثال
الرمز التالي سوف:
- ابدأ حلقة الحدث
- ابدأ خادم الويب على المنفذ 1337
- ابدأ خادم مقبس الويب على المنفذ 1338
- ثم اقرأ ملفًا و "dosomething" معه:
EventLoop.Start(() => {
//create a web server
Server.Create((req, res) => {
res.Write("<h1>Hello World</h1>");
}).Listen("http://*:1337");
//start a web socket server
Net.CreateServer((socket) => {
socket.Receive((text) => {
socket.Send("Echo: " + text);
});
}).Listen("127.0.0.1", 1338, "http://origin.com");
//Read a file
File.ReadAllText(@"C:\Foo.txt", (text) => {
DoSomething(text);
});
});
لذلك أعتقد أن إجابتي هي "نعم" ، يمكن أن يتم ذلك في C#... أو أي لغة تقريبًا لهذه المسألة. الخدعة الحقيقية هي القدرة على الاستفادة من I/O المحظورة الأصلية.
سيتم نشر مزيد من المعلومات حول المشروع هنا.
بالتأكيد ، يتطلب فقط حلقة حدث. شيء مثل:
class EventLoop {
List<Action> MyThingsToDo { get; set; }
public void WillYouDo(Action thing) {
this.MyThingsToDo.Add(thing);
}
public void Start(Action yourThing) {
while (true) {
Do(yourThing);
foreach (var myThing in this.MyThingsToDo) {
Do(myThing);
}
this.MyThingsToDo.Clear();
}
}
void Do(Action thing) {
thing();
}
}
class Program {
static readonly EventLoop e = new EventLoop();
static void Main() {
e.Start(DoSomething);
}
static int i = 0;
static void DoSomething() {
Console.WriteLine("Doing something...");
e.WillYouDo(() => {
results += (i++).ToString();
});
Console.WriteLine(results);
}
static string results = "!";
}
قريبا جدا ، سترغب في التخلص من DoSomething
وتتطلب تسجيل كل العمل مع MyThingsToDo
. ثم ، ستحتاج إلى تمرير enum
أو شيء من هذا القبيل لكل ThingToDo
هذا يخبرها لماذا تفعل شيئًا. في تلك المرحلة ، سوف تدرك أن لديك ملف مضخة الرسائل.
راجع للشغل ، أود أن أقول node.js
يتلألأ عن حقيقة أنه يعمل على نظام التشغيل والتطبيق الذي تم تعدد مؤشرات الترابط. بدون ذلك ، ستحظر كل مكالمة إلى الشبكة أو القرص.
ال الامتدادات التفاعلية لـ .NET (RX) مصمم للبرمجة غير المتزامنة والموازاة. يتيح لك البرمجة بطريقة تفاعلية مقابل التفاعلية ، غير الحظر. يمكنك استخدام مشغلات استعلام LINQ ، وأخرى جديدة لواجهات iobservable/iobserver ، والتي تعد جزءًا من RX. يوفر RX المزدوج الرياضي لـ ienumerable/ienumerator ، في شكل iobservable/iobserver ، مما يعني أنه يمكنك استخدام جميع مشغلي الاستعلام القياسي LINQ ، بطريقة إعلانية ، على عكس استخدام واجهات برمجة التطبيقات متعددة التشققات مباشرة.
ليس متأكدا مما اذا هذه هو ما تبحث عنه. .NET يمكن أن تفعل عمليات الاسترجاعات غير المتزامنة دون خيوط متعددة صريحة.
لا ينطبق مثالك على Node.js حقًا لأن الخادم الذي يعمل عليه هو أداء جميع مؤشرات الترابط المتعددة اللازمة. إذا تم تنفيذ الأحداث بناءً على نفس إشارة الساعة الخارجية ، فهي ليست غير متزامنة. يمكنك الالتفاف على هذا من خلال تشغيل تطبيق آخر وسيؤدي ذلك إلى إنشاء عمليتين على النظام.
لا توجد طريقة يمكنك من خلالها جعل التطبيق نفسه يعمل كعملية واحدة على نظام واحد يؤدي أحداثًا غير متزامنة دون وجود موضوع آخر.
لمزيد من التفاصيل ، انظر هذا سؤال غير متزامن مقابل القراءة المتعددة.
يمكنك استخدام مرسل WPF ، حتى لو كنت لا تفعل واجهة المستخدم. الرجوع إلى التجميع WindowsBase
. ثم ، في رمز بدء التشغيل الخاص بك تنفيذ:
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(Initialize));
Dispatcher.Run();
من عند Initialize
وفي أي مكان آخر في الكود الخاص بك ، استخدم Dispatcher.CurrentDispatcher.BeginInvoke
لجدولة تنفيذ أساليب ASYNC اللاحقة.
لقد قمت بتطوير خادم يعتمد على httplistener وحلقة حدث ، ودعم MVC و WebAPI والتوجيه. بالنسبة لما رأيته ، فإن العروض أفضل بكثير من المعيار IIS+MVC ، بالنسبة إلى MVCMUSICSTORE ، لقد انتقلت من 100 طلب في الثواني و 100 ٪ وحدة المعالجة المركزية إلى 350 مع 30 ٪ وحدة المعالجة المركزية. إذا كان أي شخص سيحاول تجربة ، فأنا أكافح من أجل التعليقات!
ملاحظة: أي اقتراح أو تصحيح مرحب به!
أعتقد أنه ممكن ، إليك مثال مفتوح المصدر مكتوب في VB.NET و C#:
https://github.com/perrybutler/dotnetsockets/
يستخدم نمط غير متزامن قائم على الحدث (EAP), نمط IaSyncresult وتجمع الخيوط (IOCP). سوف يقوم بتسلسل/حشد الرسائل (يمكن أن تكون الرسائل أي كائن أصلي مثل مثيل الفئة) إلى حزم ثنائية ، ونقل الحزم عبر TCP ، ثم تخلص/إلغاء تحديد الحزم في الطرف المتلقي حتى تحصل على كائنك الأصلي للعمل مع . هذا الجزء يشبه إلى حد ما protobuf أو RPC.
تم تطويره في الأصل كـ "رمز Netcode" للألعاب متعددة اللاعبين في الوقت الفعلي ، ولكنه يمكن أن يخدم العديد من الأغراض. للأسف لم أتجول في استخدامه. ربما شخص آخر سوف.
يحتوي الكود المصدر على الكثير من التعليقات ، لذا يجب أن يكون من السهل متابعته. يتمتع!
تحرير: بعد اختبارات الإجهاد وبعض التحسينات ، تمكنت من قبول 16357 عميلًا قبل الوصول إلى حدود نظام التشغيل. ها هي النتائج:
Simulating 1000 client connections over 16 iterations...
1) 485.0278 ms
2) 452.0259 ms
3) 495.0283 ms
4) 476.0272 ms
5) 472.027 ms
6) 477.0273 ms
7) 522.0299 ms
8) 516.0295 ms
9) 457.0261 ms
10) 506.029 ms
11) 474.0271 ms
12) 496.0283 ms
13) 545.0312 ms
14) 516.0295 ms
15) 517.0296 ms
16) 540.0309 ms
All iterations complete. Total duration: 7949.4547 ms
الآن هذا هو مع جميع العملاء الذين يعملون على مضيف محلي ، وإرسال رسالة صغيرة إلى الخادم مباشرة بعد الاتصال. في اختبار لاحق على نظام مختلف ، كان الخادم يتفوق على ما يزيد قليلاً عن 64000 اتصال عميل (تم الوصول إلى حد المنفذ!) في حوالي 2000 في الثانية ، ويستهلك 238 ميجابايت من ذاكرة الوصول العشوائي.
هنا مثالي من EAP أحادي الخيال. هناك العديد من تطبيقات EAP في أنواع التطبيقات المختلفة: من تطبيق وحدة التحكم البسيطة إلى تطبيق ASP.NET وخادم TCP والمزيد. جميعهم يبنون على الإطار الصغير المسمى الفردي الذي من الناحية النظرية قابلة للتوصيل مع أي نوع من تطبيق .NET.
على عكس الإجابات السابقة ، يعمل تنفيذي كوسيط بين التقنيات الحالية (ASP.NET ، مآخذ TCP ، RabbitMQ) من جانب واحد ومهام داخل حلقة الحدث من الجانب الآخر. وبالتالي فإن الهدف هو عدم تقديم تطبيق خالص واحد ولكن لدمج حلقة الحدث مع تقنيات التطبيق الحالية. وأنا أتفق تمامًا مع المنشور السابق على أن .NET لا يدعم التطبيقات المفردة المفردة.