Безопасно ли распространять локальную переменную между потоками (через закрытие обратного вызова)?
Я хочу сделать что-то вроде следующего - в основном я вызываю операцию async, которая вызовет обратный вызов в другом потоке, и я хочу дождаться завершения его "inline". Мое беспокойство заключается в том, что изменения переменных, разделяемых потоками (строка и событие), могут не синхронизироваться из-за того, что они хранятся в регистре, например. Если бы они были переменными-членами, я мог бы отметить их изменчивым, но волатильным не может использоваться для локальных переменных, созданных в стеке. Я мог бы использовать переменные-члены, но я думаю, что его очиститель не загромождает мой класс, сохраняя его локальным.
Bar bar = null;
ManualResetEvent event = new ManualResetEvent(false);
foo.AsyncOperation(new Action(()=>{
// This delegate will be called in another thread
bar = ...
event.Set();
}));
event.WaitOne(timeout);
// use bar
Ответы
Ответ 1
Да, он будет работать правильно. Читайте здесь
http://www.albahari.com/threading/part4.aspx
The following implicitly generate full fences:
Setting and waiting on a signaling construct
а в конструкциях сигнализации включается ManualResetEvent
.
Если вы хотите знать, что такое full fence
, на той же странице:
Полные заборы Простейший вид барьера памяти - это полная память барьер (полный забор), который предотвращает любое переупорядочение команд или кеширование вокруг этого забора. Вызов Thread.MemoryBarrier генерирует полный забор;
Ответ 2
Я думаю, что ваш код будет работать - закрытие будет подниматься затем в кучу, даже если они были просто переменными стека (например, ManualReseetEvent не будет).
Но почему бы вам не поместить все после event.WaitOne() только внутри продолжения (блок был вызван event.Set)? Я думаю, что это должен быть предпочтительный способ справиться с такой ситуацией, и вы не столкнетесь с такими неприятностями (вам вообще не нужна панель во внешнем блоке, и вы все равно можете использовать MRE для проверки).
Я бы подумал превратить это в Operations с помощью объектов Task - это решит все это за один раз (например, верните задачу из вашего AsyncOperation). Вы можете дождаться результата Задачи и использовать возвращенный бар...
class Foo
{
// ...
private Task<Bar> AsyncOperation(Task<Bar> initializeBar)
{
return initializeBar
.ContinueWith(
bar => {
/* do your work with bar and return some new or same bar */
return bar;
});
}
}
и используйте его следующим образом:
var task = foo.AsyncOperation(Taks.Factory.StartNew(() => { /* create and return a bar */ }));
var theBar = task.Result; // <- this will wait for the task to finish
// use your bar
PS: Закрытие в основном приведет их только к классу-объекту;)
PPS: мне сложно протестировать этот код без вашего AsyncOperation, но он должен работать с ошибками модульного синтаксиса с помощью неправильного написания/ввода текста. Я сделал