Разница между delegate.BeginInvoke и использованием потоков ThreadPool в C#

В С# есть ли разница между использованием делегата для асинхронного выполнения некоторой работы (вызовом BeginInvoke()) и использованием потока ThreadPool, как показано ниже

public void asynchronousWork(object num)
    {
        //asynchronous work to be done
        Console.WriteLine(num);
    }

 public void test()
    {
        Action<object> myCustomDelegate = this.asynchronousWork;
        int x = 7;

        //Using Delegate
        myCustomDelegate.BeginInvoke(7, null, null);

        //Using Threadpool
        ThreadPool.QueueUserWorkItem(new WaitCallback(asynchronousWork), 7);
        Thread.Sleep(2000);
    }

Изменить.
BeginInvoke гарантирует, что для выполнения асинхронного кода используется поток из пула потоков, так что есть ли разница?


person nighthawk457    schedule 26.04.2012    source источник
comment
возможный дубликат Использует ли Func‹T›.BeginInvoke ThreadPool?   -  person nothrow    schedule 27.04.2012
comment
Я не уверен, был ли ваш комментарий до того, как я отредактировал свой вопрос, но я знаю, что BeginInvoke выполняет делегаты в потоках, принадлежащих пулу потоков. Мой вопрос был больше о том, есть ли какая-либо разница между явным выполнением метода в пуле потоков с использованием QueueUserWorkItem или с использованием BeginInvoke   -  person nighthawk457    schedule 27.04.2012


Ответы (1)


Джо Даффи в своей книге Concurrent Programming on Windows (стр. 418) говорит о Delegate.BeginInvoke следующее:

Все типы делегатов по соглашению предлагают методы BeginInvoke и EndInvoke наряду с обычным синхронным методом Invoke. Хотя это хорошая функция модели программирования, вы должны держаться подальше от них, где это возможно. Реализация использует инфраструктуру удаленного взаимодействия, которая накладывает значительные накладные расходы на асинхронный вызов. Работа с очередью напрямую в пул потоков часто является лучшим подходом, хотя это означает, что вам придется самостоятельно координировать логику рандеву.

РЕДАКТИРОВАТЬ: я создал следующий простой тест относительных накладных расходов:

int counter = 0;
int iterations = 1000000;
Action d = () => { Interlocked.Increment(ref counter); };

var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
for (int i = 0; i < iterations; i++)
{
    var asyncResult = d.BeginInvoke(null, null);
}

do { } while(counter < iterations);
stopwatch.Stop();

Console.WriteLine("Took {0}ms", stopwatch.ElapsedMilliseconds);
Console.ReadLine();

На моей машине вышеуказанный тест выполняется примерно за 20 секунд. Замена вызова BeginInvoke на

System.Threading.ThreadPool.QueueUserWorkItem(state =>
{
    Interlocked.Increment(ref counter);
});

изменяет время работы на 864 мс.

person Lee    schedule 26.04.2012
comment
Я чувствую, что начинается еще один коленный рефлекс? Нам нужны некоторые эмпирические данные, чтобы объективировать значительные накладные расходы на асинхронный вызов. Какой компромисс связан с необходимостью самостоятельно координировать логику рандеву... по сравнению с синтаксический сахар общепринятых асинхронных реализаций - например Бегининвок, Обратный вызов, ЭндИнвок. Одна вещь, которую мы получаем от использования IAsyncResult, — это дескриптор, с помощью которого мы можем отслеживать происходящее. На самом деле я никогда ничего не пробовал с помощью ThreadPool, чтобы посмотреть, смогу ли я сделать то же самое. - person IAbstract; 27.04.2012
comment
Интересно, почему каждый делегат определяет метод BeginInvoke, который асинхронно запускает делегат? Единственным аспектом BeginInvoke, который может показаться привязанным к какой-либо конкретной подписи делегата, будет процесс преобразования делегата и параметров в унифицированный элемент, что можно было бы сделать так же хорошо, если бы каждый делегат определял метод Bind, принимающий те же параметры, что и сигнатура делегата. делегат и вернул MethodInvoker. - person supercat; 27.04.2012
comment
@IAbstract - это конкретно о Delegate.Begin\EndInvoke, а не об общем BeginInvoke\EndInvoke шаблоне, найденном в фреймворке. Это правда, что IAsyncResult дает вам больше контроля, однако от него отказываются в пользу Task в .net 4 и выше. - person Lee; 27.04.2012
comment
@Lee: мне было интересно использовать Task вместо старого шаблона и IAsyncResult. Спасибо за разъяснение и некоторые эмпирические данные! - person IAbstract; 27.04.2012
comment
@Lee: Я получил несколько очень разных результатов для первого теста - Action.BeginInvoke(...): ~ 10 секунд на каждый запуск, что в среднем составляет около 100 выполнений / мс. ThreadPool постановка в очередь в среднем составляет 2,2 тыс. выполнений/мс, а Task.Start() — 1,7 тыс. выполнений/мс. Я могу опубликовать свои тесты и соответствующие коды, если вы хотите перепроверить мои результаты. ;) Чтобы мое тестирование соответствовало вашему, я использую тот же Interlocked.Increment(ref counter) в своем методе делегата. - person IAbstract; 28.04.2012