Как я могу переключаться между основным и фоновым потоком в C #?

1

Я хотел бы выполнить некоторый код в фоновом потоке и периодически использовать код фона в основном потоке. Что-то вроде следующего:

void functionThatMustBeRunOnMainThread(string arg) {
    Debug.Print("On main thread: "+arg);
}

// Run the task asynchronously. On completion, call completionAction in the main thread.
void launchAsyncBackgroundTask( Action<bool> completionAction ) {
    performInArbitraryBackgroundThread(() => {
        // do first long running thing
        sleep(10);

        // notify main thread
        performOnMainThread(() => {
            functionThatMustBeRunOnMainThread("phase 1");
        });

        // do second long running thing
        sleep(10);

        // notify main thread
        performOnMainThread(() => {
            functionThatMustBeRunOnMainThread("phase 2");
        });

        // do final long running thing
        sleep(10);

        performOnMainThread(() => {
            Debug.Print("Done!");
            completionAction(true);
        });
    });
}

Я знаю о BackgroundWorker но он не предлагает гибкости, которую я ищу.

Здесь два пункта -

  1. Я перехожу назад к основному потоку несколько раз - дважды во время выполнения, а затем в третий раз для выполнения обратного вызова завершения запроса пользователем.
  2. Код очень читабельен. Несмотря на то, что задействованы два потока, синхронизация подразумевается или обрабатывается в другом месте - чтение сверху вниз последовательность событий ясна, хотя бы с идеализированной перспективы. Нет никаких статических функций или дополнительных классов для переопределения - все это происходит inline с лямбда-выражениями/замыканиями.

Это обычное дело в Obj-C с помощью Grand Central Dispatch (это работает в основном так, как описано выше). Есть ли эквивалент С#?

  • 3
    Используйте await Task.Run() .
  • 0
    Я не считаю такой код тривиальным или читабельным по сравнению с кодом, который вы можете написать с помощью TPL.
Показать ещё 1 комментарий
Теги:
multithreading
async-await
task-parallel-library

2 ответа

4
Лучший ответ

Вы можете легко достичь того, что вам нужно, с помощью async-await вместе с Task Parallel Library:

В этом примере предполагается, что MethodThatDoesStuffInBackground или любой из других трудоемких методов - это операции с ЦП. Если нет, и они делают IO, вы можете отказаться от использования Task.Run:

(Этот метод должен быть вызван из потока пользовательского интерфейса для правильной работы)

public async Task DoStuff()
{
    await Task.Run(() => MethodThatDoesStuffInBackground());

    FunctionThatMustRunOnMainThread();

    await Task.Run(() => MethodThatDoesMoreStuffInBackground());

    FunctionThatMustRunOnMainThread();

    await Task.Run(() => EvenMoreWorkInBackgroundThread());

    FunctionThatMustRunOnMainThread();
}
  • 0
    И не забудьте вызвать этот метод DoStuff из потока пользовательского интерфейса.
  • 1
    Добавьте Task.Delay для асинхронных задержек, которые не блокируют потоки, возможно, Progress<T> для сигнализации между потоками, используя сложные объекты событий
Показать ещё 11 комментариев
1

Я рекомендую использовать Task.Run для фоновой работы, IProgress<T> для обновления прогресса и Task для уведомления о завершении. Такой подход позволяет сохранить фоновую логику в одном месте, отдельно от пользовательского интерфейса.

Что-то вроде этого:

// Run the task asynchronously. On completion, call completionAction in the main thread.
async Task launchBackgroundTaskAsync( Action<bool> completionAction ) {
  var progress = new Progress<string>(arg => {
      Debug.Print("On main thread: "+arg);
  };

  await Task.Run(() => BackgroundLogic(progress));
  completionAction(true);
}

void BackgroundLogic(IProgress<string> progress) {
  // do first long running thing
  sleep(10);

  // notify main thread
  if (progress != null)
    progress.Report("phase 1");

  // do second long running thing
  sleep(10);

  // notify main thread
  if (progress != null)
    progress.Report("phase 2");

  // do final long running thing
  sleep(10);

  if (progress != null)
    progress.Report("Done!");
}

Обратите внимание, что completionAction больше не надо, так как launchBackgroundTaskAsync сама возвращает Task. Это может быть просто удалено без потери возможности - просто звонящие этого использование метода await:

async Task launchBackgroundTaskAsync() {
  var progress = new Progress<string>(arg => {
      Debug.Print("On main thread: "+arg);
  };

  await Task.Run(() => BackgroundLogic(progress));
}
  • 0
    Так что я могу просто добавить любой произвольный код в Progress ? то есть я должен рассматривать Progress как синоним для performOnMainThread ? Вызывается ли он синхронно с точки зрения потока BG (т. Е. Фоновый поток блокируется до тех пор, пока progress.Repert() возвращается), или async (операция фонового потока продолжается, пока progress.Report() выполняет async в основном потоке)?
  • 1
    Progress<T> принимает делегат Action<T> , поэтому вы можете поместить в него любой код. Он работает асинхронно , поэтому progress.Report не будет блокировать фоновый поток.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню