Как заблокировать объект с помощью F #?
Предположим, у меня есть следующий код:
let a = ref 4.
printfn "1) a = %g" !a
let t1 = System.Threading.Thread (fun () ->
lock a (fun () ->
printfn "locked"
System.Threading.Thread.Sleep 1000
printfn "unlocked") )
t1.Start()
System.Threading.Thread.Sleep 100
a := 8.
printfn "2) a = %g" !a
Что дает следующий результат:
1) a = 4
заблокирован
2) a = 8
val a: float ref = {contents = 8.0;}
val t1: System.Threading.Thread
разблокирована
Почему a
равно 8.
, когда я заблокировал его? Можно ли заблокировать запись с изменяемыми значениями и ссылками?
PS: Мне нужно заблокировать объект, к которому я одновременно обращаюсь, и в то же время WCF.
Ответы
Ответ 1
Я согласен с @Dmitry, что использование lock k (fun () -> ...)
не означает, что вы предотвращаете мутацию на k
; это означает, что вы получаете ключ k
для доступа к объекту. Поскольку ключ уникален, у вас есть взаимный эксклюзивный доступ к объекту, чтобы избежать неправильных результатов.
На основе вашего примера, запуск этого ниже кода в режиме Отладка приводит к результатам 1, 3 или 6 для a
произвольно. Эти значения могут быть объяснены ситуацией, когда один поток обращается к старому значению a
, а другой поток пытается обновить эту ячейку.
let a = ref 4;;
printfn "1) a = %i" !a
let t1 = System.Threading.Thread (fun () ->
printfn "locked in thread 1"
a:= !a + 2
printfn "unlocked in thread 1"
)
let t2 = System.Threading.Thread (fun () ->
printfn "locked in thread 2"
a:= !a - 3
printfn "unlocked in thread 2"
)
t1.Start()
t2.Start()
System.Threading.Thread.Sleep 1000 // wait long enough to get the correct value
printfn "2) a = %i" !a;;
System.Console.ReadKey() |> ignore;;
Чтобы обеспечить правильный результат (который должен быть 3), вы можете ввести объект monitor
, и любой поток, который хотел бы обновить a
, должен сначала получить monitor
:
let monitor = new Object()
let a = ref 4;;
printfn "1) a = %i" !a
let t1 = System.Threading.Thread (fun () ->
printfn "locked in thread 1"
lock monitor (fun () -> a:= !a + 2)
printfn "unlocked in thread 1"
)
let t2 = System.Threading.Thread (fun () ->
printfn "locked in thread 2"
lock monitor (fun () -> a:= !a - 3)
printfn "unlocked in thread 2"
)
t1.Start()
t2.Start()
System.Threading.Thread.Sleep 1000 // wait long enough to get the correct value
printfn "2) a = %i" !a;;
System.Console.ReadKey() |> ignore;;
Ответ 2
Кажется, вы неправильно поняли, как работает блокировка.
В С# ключевое слово lock гарантирует, что один поток не войдет в критический раздел кода, а другой поток находится в критическом разделе. Если другой поток пытается ввести заблокированный код, он будет ждать, блокировать, пока объект выпущен." Поэтому он не защищает заблокированный объект от мутации. А в F # lock
работает точно так же.
Кстати, AFAIK, lock
- это всего лишь сахара в классе Monitor.
И согласно Дону Симу определение функции блокировки действительно выглядит так:
open System.Threading
let lock (lockobj:obj) f =
Monitor.Enter lockobj
try
f()
finally
Monitor.Exit lockobj
ОБНОВЛЕНИЕ:. Поскольку блокировка не делает объект доступным только для чтения и поскольку у вас нет контроля над кодом WPF, решения вашей проблемы включают добавление синхронизации потоков к свойствам WPF-доступа (и не пытайтесь блокировать поток пользовательского интерфейса) или планировать работу с потоком пользовательского интерфейса или другим. Трудно сказать, не зная точной проблемы. Хорошо, хорошо, что в Интернете много информации.
UPDATE2: К сожалению, я прочитал "WPF" вместо "WCF". Это облегчает вашу жизнь. Вам просто нужно добавить синхронизацию потоков к вашей реализации методов WCF, и в большинстве случаев вы можете перестать беспокоиться о ее блокировке. Поэтому просто аккуратно добавьте блокировки ко всему соответствующему коду...