Подтвердить что ты не робот

Как заблокировать объект с помощью 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.

4b9b3361

Ответ 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, и в большинстве случаев вы можете перестать беспокоиться о ее блокировке. Поэтому просто аккуратно добавьте блокировки ко всему соответствующему коду...