如何在C#中切换主线程和后台线程?

时间:2015-01-19 14:32:03

标签: c# .net multithreading task-parallel-library async-await

我想在后台线程中执行一些代码,并让后台代码定期在主线程上执行代码。如下所示:

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. 我回电话&#39;到主线程多次 - 执行期间两次,然后第三次执行用户提供的完成回调。
  2. 代码非常易读。即使涉及两个线程,同步也是隐含的或在其他地方处理 - 从上到下读取事件序列是明确的,如果仅从理想化的角度来看。没有静态函数或额外的类可以覆盖 - 它们都与lambda表达式/闭包一起发生。
  3. 使用Grand Central Dispatch在Obj-C中做这件事是微不足道的(它几乎如上所述)。是否有C#等价物?

2 个答案:

答案 0 :(得分:4)

您可以使用async-await以及Task Parallel Library轻松实现您的需求:

此示例假定您的MethodThatDoesStuffInBackground或任何其他耗时的方法都是CPU绑定操作。如果没有,他们正在做IO,你可以放弃使用Task.Run

(应该从UI线程调用此方法才能正常工作)

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

    FunctionThatMustRunOnMainThread();

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

    FunctionThatMustRunOnMainThread();

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

    FunctionThatMustRunOnMainThread();
}

答案 1 :(得分:1)

我建议将Task.Run用于后台工作,IProgress<T>用于进度更新,Task用于完成通知。这种方法使您可以将背景逻辑保留在一个位置,与UI分开。

这样的事情:

// 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));
}