如何在任务延续块中序列化线程执行?

时间:2018-04-21 22:42:23

标签: uwp task c++-cx ppl

在下面的示例中,对HandleChangesAsync的调用是在异步任务中通过事件处理程序进行的。

问题 - 有没有办法确保一次只有一个线程可以执行HandleChangesAsync create_task +任务延续块(即使任务继续块调用其他异步函数)?

请注意,我不能只使用同步原语,因为HandleChangesAsync可以在异步操作完成之前返回。

void MyClass::DoStuffAsync()
{    
    WeakReference weakThis(this);
    create_task(DoMoreStuffAsync())
        .then([weakThis]())
    {
        auto strongThis = weakThis.Resolve<HomePromotionTemplateViewModel>();
        if (strongThis)
        {
            strongThis->RegisterForChanges();
            strongThis->HandleChangesAsync();
        }
    });
}

void MyClass::RegisterForChanges()
{    
    // Attach event handler to &MyClass::OnSomethingChanged
}

void MyClass::OnSomethingChanged()
{
    HandleChangesAsync();
}

void MyClass::HandleChangesAsync()
{    
    WeakReference weakThis(this);
    create_task(DoMoreCoolStuffAsync(m_needsProtection))
        .then([weakThis]()
    {
        // do async stuff 
        // update m_needsProtection
    })
        .then([weakThis]()
    {
        // do more async stuff 
        // update m_needsProtection
    });
}

1 个答案:

答案 0 :(得分:1)

假设你只是想忽略重叠请求(而不是排队),这很容易通过原子布尔来实现。可以将以下内容粘贴到新的XAML页面中,然后只检查输出窗口。有两个主题每个都会调用DoStuff(),但其中只有一个会一次执行 - value_的值始终是016完成工作;从来没有别的。如果多个线程同时执行工作,您可能会得到其他数字。

“做工作”有一堆愚蠢的代码,但基本的代码在compare_exchange_strong() call。它基本上说“我希望busy_的值为false,在这种情况下,将busy_更新为true并返回true(在这种情况下,我将开始做工作。)但是如果busy_ 的值不是已经false,那么返回false(我将不会做任何工作)”。 请注意!内的逻辑否定if: - )

我不是内存排序方面的专家,所以如果你在一个紧凑的循环中运行,有可能有一种更有效的方法(即传递一个显式的memory_order值),但它应该是正确的并且应该足以支持正常的用户体验工作:

#include <atomic>
#include <ppltasks.h>
#include <string>

using namespace concurrency;

class Test
{
  std::atomic<bool> busy_;
  int value_;

  task<int> AddNumbersAsync(int x, int y)
  {
    return task<int>{[x, y]
    {
      Sleep(20);
      return x + y;
    }};
  }

  void CompleteStuff()
  {
    OutputDebugStringA("** Done with work; value is ");
    OutputDebugStringA(std::to_string(value_).c_str());
    OutputDebugStringA("\r\n");
    busy_ = false;
  }

public:
  Test()
    : busy_{ false }, value_{ 0 }
  {}

  void DoStuff()
  {
    // ---
    // This is where the magic happens...
    // ---
    bool expected{ false };
    if (!busy_.compare_exchange_strong(expected, true))
    {
      OutputDebugStringA("Work already in progress; bailing.\r\n");
      return;
    }

    OutputDebugStringA("Doing work...\r\n");
    value_ = 2;

    try
    {
      AddNumbersAsync(value_, value_).then([this](int i)
      {
        value_ = i;
        return AddNumbersAsync(value_, value_);
      }).then([this](int i)
      {
        value_ = i;
        return AddNumbersAsync(value_, value_);
      }).then([this](task<int> i)
      {
        // ---
        // Handle any async exceptions
        // ---
        try
        {
          value_ = i.get();
        }
        catch (...)
        {
          OutputDebugStringA("Oops, an async exception! Resetting value_\r\n");
          value_ = 0;
        }
        CompleteStuff();
      });
    }
    // ---
    // Handle any sync exceptions
    // ---
    catch (...)
    {
      OutputDebugStringA("Oops, an exception! Resetting value_\r\n");
      value_ = 0;
      CompleteStuff();
    }
  }
};

Test t;

task<void> TestTask1;
task<void> TestTask2;

MainPage::MainPage()
{
  InitializeComponent();
  TestTask1 = task<void>([]
  {
    for (int i = 0; i < 100; i++)
    {
      t.DoStuff();
      Sleep(20);
    }
  });

  TestTask1 = task<void>([]
  {
    for (int i = 0; i < 100; i++)
    {
      t.DoStuff();
      Sleep(30);
    }
  });
}

请注意,我们需要捕获同步异常(可能由任务链的第一部分引起的异常)和异步异常(由其中一个任务或其继续引起的异常)。我们通过使用类型task<int>的延续来捕获异步异常,然后通过在任务上显式调用get()来检查异常。在这两种情况下,我们都会重置value_busy_标记。

您应该看到一些输出,如下所示;你可以知道多个线程互相竞争,因为有时OutputDebugString调用会交错:

Doing work...
Work already in progress; bailing.
Work already in progress; bailing.
Work already in progress; bailing.
Work already in progress; bailing.
** Done with work; value is Work already in progress; bailing.
16