Возможно ли в .NET, используя C #, добиться асинхронного шаблона на основе событий без многопоточности? - PullRequest
14 голосов
/ 16 мая 2010

Я поражен архитектурным дизайном Node.js , и мне было интересно, способен ли C # на такой дизайн:

Асинхронный, основанный на событиях / цикл событий, неблокирующий Ввод / вывод без многопоточности.

Ответы [ 10 ]

13 голосов
/ 16 мая 2010

Я думаю, что все операции BeginXyz, которые реализуют стандартную модель асинхронного программирования, запускают обратный вызов в потоке пула потоков, что делает приложение автоматически многопоточным.

Однако вы можете достичь однопоточной модели асинхронного программирования, синхронизируя все операции через единый поток GUI, который поддерживается для приложений 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), поэтому вы будете использовать поток пула потоков только для пересылки обратной передачи обратно в исходный (единственный) поток.

В качестве дополнительного примечания, это напрямую поддерживается асинхронными рабочими процессами в F # и может использоваться для довольно элегантного стиля программирования графического интерфейса , как описано здесь . Я не знаю node.js, но я полагаю, что вы также можете быть удивлены асинхронными рабочими процессами F # , поскольку они действительно хороши для асинхронного / основанного на событиях / ... стиля программирования: -)

7 голосов
/ 22 июня 2012

Я работаю над такой вещью в .NET, как проект для домашних животных. Я называю это ALE (Другое Looping Event) ... потому что пиво.

Это чрезвычайно альфа прямо сейчас, но это все доморощенный, потому что, как и вы, я хотел знать, можно ли это сделать.

  • Это архитектура цикла событий.
  • Использует неблокирующие асинхронные вызовы ввода / вывода .NET
  • Он использует вызовы в стиле обратного вызова, чтобы помочь разработчикам писать асинхронный код, который немного более читабелен.
  • Асинхронная реализация веб-сокетов
  • Реализация асинхронного http-сервера
  • Реализация асинхронного sql-клиента

В отличие от некоторых других попыток, я видел, что это не просто веб-сервер с циклом событий. Вы можете написать любое приложение на основе цикла событий.


* * Пример тысячи двадцать-шести * 1 028 *

Следующий код будет:

  • Запустить цикл событий
  • Запустить веб-сервер через порт 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 # ... или почти на любом языке в этом отношении. Настоящая хитрость заключается в возможности использовать собственный неблокирующий ввод / вывод.

Более подробная информация о проекте будет размещена здесь .

6 голосов
/ 16 мая 2010

Конечно, просто требуется цикл обработки событий. Что-то вроде:

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 скрывает тот факт, что он работает в ОС и многопоточном приложении. Без этого каждый вызов сети или диска будет блокироваться.

3 голосов
/ 16 мая 2010

Реактивные расширения для .NET (Rx) предназначены для асинхронного и параллельного программирования. Это позволяет программировать реактивным и интерактивным способом, не блокируя. Вы используете операторы запросов LINQ и новые для интерфейсов IObservable / IObserver, которые являются частью Rx. Rx предоставляет математический двойник IEnumerable / IEnumerator в форме IObservable / IObserver, что означает, что вы можете использовать все стандартные операторы запросов LINQ декларативным способом, а не напрямую использовать многопоточные API.

2 голосов
/ 16 мая 2010

ваш пример для node.js неприменим, поскольку сервер, на котором он работает, выполняет всю необходимую многопоточность. Если события выполняются на основе одного и того же внешнего тактового сигнала, то они не асинхронные. Вы можете обойти это, запустив другое приложение, которое создаст в системе два процесса.

Невозможно заставить одно и то же приложение, работающее как один процесс в одной системе, выполнять асинхронные события без использования другого потока.

Подробнее см. Асинхронный и многопоточный вопрос .

2 голосов
/ 16 мая 2010

Не уверен, что это - это то, что вы ищете. .NET может выполнять асинхронные обратные вызовы без явного многопоточности.

1 голос
/ 21 июня 2012

Вы можете использовать диспетчер WPF, даже если вы не используете пользовательский интерфейс. Ссылка на сборку WindowsBase. Затем в вашем коде запуска выполните:

Dispatcher.CurrentDispatcher.BeginInvoke(new Action(Initialize));
Dispatcher.Run();

Начиная с Initialize и в других местах вашего кода, используйте Dispatcher.CurrentDispatcher.BeginInvoke для планирования выполнения последующих асинхронных методов.

0 голосов
/ 25 октября 2014

Вот мой пример однопоточного EAP. Существует несколько реализаций EAP в разных типах приложений: от простого консольного приложения до приложения asp.net, tcp-сервера и других. Все они построены на крошечной инфраструктуре, называемой SingleSand , которая теоретически может подключаться к любому типу .net-приложения.

В отличие от предыдущих ответов, моя реализация выступает в качестве посредника между существующими технологиями (asp.net, tcp sockets, RabbitMQ) с одной стороны и задачами внутри цикла обработки событий с другой стороны. Поэтому цель состоит не в том, чтобы сделать чисто однопоточное приложение, а в том, чтобы интегрировать цикл обработки событий в существующие технологии приложений. Я полностью согласен с предыдущим постом, что .net не поддерживает чисто однопоточные приложения.

0 голосов
/ 13 августа 2014

Я считаю, что это возможно, вот пример с открытым исходным кодом, написанный на VB.NET и C #:

https://github.com/perrybutler/dotnetsockets/

Используется Асинхронный шаблон на основе событий (EAP) , Шаблон IAsyncResult и пул потоков ( IOCP ). Он будет сериализовать / маршалировать сообщения (сообщения могут быть любым собственным объектом, например экземпляром класса) в двоичные пакеты, передавать пакеты по TCP, а затем десериализовывать / демаршировать пакеты на принимающей стороне, чтобы вы могли работать с вашим собственным объектом. , Эта часть чем-то похожа на Protobuf или RPC.

Первоначально он был разработан как «сетевой код» для многопользовательских игр в реальном времени, но он может служить многим целям. К сожалению, я никогда не удосужился использовать его. Может быть, кто-то еще будет.

В исходном коде много комментариев, поэтому за ним должно быть легко следить. Наслаждайтесь!

РЕДАКТИРОВАТЬ: После стресс-тестов и нескольких оптимизаций он смог принять 16 357 клиентов до достижения пределов ОС. Вот результаты:

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

Теперь все клиенты работают на локальном хосте и отправляют небольшое сообщение на сервер сразу после подключения. В последующем тесте в другой системе сервер работал с максимальной скоростью чуть более 64 000 клиентских подключений (достигнут предел порта!) Со скоростью около 2000 в секунду, потребляя 238 МБ ОЗУ.

0 голосов
/ 17 апреля 2014

Я разработал сервер на основе HttpListener и цикла обработки событий, поддерживающего MVC, WebApi и маршрутизацию. Для того, что я видел, производительность намного лучше, чем стандартная IIS + MVC, для MVCMusicStore я перешел со 100 запросов в секунду и 100% CPU на 350 с 30% CPU. Если кто-нибудь попробует, я борюсь за отзывы!

PS любые предложения или исправления приветствуются!

...