在下面的示例中,对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
});
}
答案 0 :(得分:1)
假设你只是想忽略重叠请求(而不是排队),这很容易通过原子布尔来实现。可以将以下内容粘贴到新的XAML页面中,然后只检查输出窗口。有两个主题每个都会调用DoStuff()
,但其中只有一个会一次执行 - value_
的值始终是0
或16
完成工作;从来没有别的。如果多个线程同时执行工作,您可能会得到其他数字。
“做工作”有一堆愚蠢的代码,但基本的代码在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