Какой самый простой способ перенести какую-то работу в фоновый поток? - PullRequest
3 голосов
/ 10 января 2012

Предположим, у меня есть этот кусок кода.

foreach(string value in myList)
  {
      string result = TimeConsumingTask(value);
      //update UI
      listBox.Add(result);
  }

Я хочу переместить string result = TimeConsumingTask(value); в какой-то фоновый поток.Какой самый простой, но эффективный способ переместить это в фоновый поток, если

- Order does matter
- Order doesn't matter

Ответы [ 7 ]

3 голосов
/ 10 января 2012

В приложении с графическим интерфейсом, таком как WPF, WinForms, Silverlight, ... вы можете использовать BackgroundWorker , поскольку он заботится о маршалинге различных обратных вызовов, которые происходят в основном потоке, что важно при обновленииUI.В ASP.NET вы можете взглянуть на асинхронных страниц .

Давайте рассмотрим пример, если порядок имеет значение с фоновым работником:

var worker = new BackgroundWorker();
worker.DoWork += (obj, args) =>
{
    args.Result = myList.Select(TimeConsumingTask).ToList();
};
worker.RunWorkerCompleted += (obj, args) =>
{
    var result = (IList<string>)args.Result;
    foreach(string value in result)
    {
        listBox.Add(result);
    }
}; 
worker.RunWorkerAsync();
2 голосов
/ 10 января 2012

Простой способ гарантировать порядок - последовательно выполнять все задачи TimeConsumingTask в одном фоновом потоке.

Это может занять немного больше времени, чем выполнение нескольких задач TimeConsumingTask в нескольких потоках, но избавит вас от головной боли при попытке синхронизировать результаты и произвести вывод в требуемом порядке.

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

Если ваша основная задача - ускорить вычисление задач TimeConsumingTasks, то, вероятно, лучше использовать несколько потоков, так как он с большей вероятностью использует преимущества нескольких ядер и выполняет часть работы параллельно.

Чтобы сохранить порядок в случае с несколькими потоками, вам придется упорядочить результаты самостоятельно. Вы можете либо сохранить временный список аккумулятора и вставить каждый результат в него в соответствующем месте результата в списке (если местоположение / индекс легко вычисляются или известны), либо добавить дополнительный этап обработки после того, как все потоки завершены, чтобы объединить несколько результатов в окончательный вывод в нужном порядке. В параллельных и распределенных вычислительных кругах процесс разбиения задачи на несколько кусков, выполняемых параллельно, а затем объединения результатов в согласованный порядок называется «сокращением карты».

1 голос
/ 10 января 2012

Я бы использовал задачу.Таким образом, вся операция выполняется в фоновом режиме, не блокируя пользовательский интерфейс.Обязательно обновите список в главном потоке.Я не уверен, как это сделать в WinForms, но в WPF вы делаете это, используя Dispatcher.Invoke() или Dispatcher.BeginInvoke(), как показано ниже.

Если заказ имеет значение:

Task.Factory.StartNew(
    () =>
        {
            foreach (string value in myList)
            {
                string result = TimeConsumingTask(value);
                //update UI
                Application.Current.Dispatcher.BeginInvoke(
                    (Action)(() => listBox.Add(result)));
            }            
        });

Если заказне имеет значения, что вы также можете использовать Parallel.ForEach().

Task.Factory.StartNew(
    () =>
        {
            Parallel.ForEach(myList, 
                value =>
                {
                    string result = TimeConsumingTask(value);
                    //update UI
                    Application.Current.Dispatcher.BeginInvoke(
                       (Action)(() => listBox.Add(result)));
                });            
        });
    }
1 голос
/ 10 января 2012

Есть много способов снять шкуру с этой кошки.За последние несколько лет я использовал много, BackgroundWorker - самый распространенный и простой.Однако с этого момента вы должны думать, что Async CTP - это путь.По крайней мере, просмотрите вступление , прежде чем принять решение.С точки зрения простоты и четкости, краткости кода, это один из самых горячих кодов, которые когда-либо были написаны.:)

1 голос
/ 10 января 2012

Вы должны дождаться результата, прежде чем сможете обновить пользовательский интерфейс, поэтому нет смысла перемещать только TimeConsumingTask () в фоновый поток. Вам нужно будет обновить пользовательский интерфейс из фонового потока (но направить вызов в поток пользовательского интерфейса).

Это зависит от вашей конечной цели, но одно простое решение - просто начать задание. Это будет работать, если порядок имеет значение.

Task.Factory.StartNew(
    () => 
    {
        foreach(string value in myList)   
        {       
            string result = TimeConsumingTask(value);       
            listBox.Invoke(new Action(() => listBox.Add(result)));   
        } 
    });

Использование BackgroundWorker также довольно просто, но не так просто, так как кода больше:

  • создать BackgroundWorker
  • назначить обработчик DoWork
  • назначить обработчик прогресса
  • set WorkerReportsProgress
  • call RunWorkerAsync ()
0 голосов
/ 10 января 2012

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

С фоновой обработкой - используя BackgroundWorker, как предлагает Дарин, - вы можете позволить пользователю продолжать работать в пользовательском интерфейсе, не дожидаясь завершения фонового процесса. Если пользователь должен дождаться окончания обработки, прежде чем он сможет продолжить, то нет смысла запускать его в фоновом режиме.

При многопоточности - при использовании Parallel Extensions Library , возможно - вы можете использовать несколько потоков для параллельного запуска повторяющегося цикла, чтобы он завершался быстрее. Это будет полезно, если ваш пользователь ожидает завершения процесса, прежде чем он сможет продолжить.

Наконец, если вы запускаете что-то в фоновом режиме, вы можете заставить его завершаться быстрее, используя многопоточность.

0 голосов
/ 10 января 2012

Если порядок не имеет значения, вы можете использовать цикл Parallel.ForEach. ( Отредактировано в соответствии с комментариями dthorpe и ttymatty ).

using System.Threading.Tasks;

var results = new List<string>();

Parallel.ForEach(myList, value =>
{
    string result = TimeConsumingTask(value);
    results.Add(result);
});

//update UI
listBox.AddRange(results);

Если вы хотите отозвать пользовательский интерфейс с информацией о процессе, я бы рекомендовал использовать BackgroundWorker , как рекомендует Дарин Димитров в своем ответе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...