Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to lock an object using F#?

Tags:

locking

f#

Assume I have the following code:

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

Which gives the following result:

1) a = 4
locked
2) a = 8

val a : float ref = {contents = 8.0;}
val t1 : System.Threading.Thread

unlocked

Why does a equals 8. when I locked it? Is it possible to lock a record with mutable values and refs?

PS: I need to lock an object which is both accessed by me and at the same time by WCF.

like image 975
Oldrich Svec Avatar asked Nov 22 '11 13:11

Oldrich Svec


2 Answers

It seems that you misunderstand how locking works.

In C#, "The lock keyword ensures that one thread does not enter a critical section of code while another thread is in the critical section. If another thread tries to enter a locked code, it will wait, block, until the object is released." So it doesn't protect locked object from mutation. And in F# lock works exactly in the same way.

By the way, AFAIK, lock is just a sugar around Monitor class.

And according to Don Syme the definition of lock function actually looks like:

open System.Threading
let lock (lockobj:obj) f =
  Monitor.Enter lockobj
  try
    f()
  finally
    Monitor.Exit lockobj

UPDATE: Because locking doesn't make an object read-only and because you have no control over WPF code, solutions to your problem involves adding thread synchronization to properties WPF accesses (and try not to block UI thread) or scheduling work on the UI thread or else. It's hard to tell without knowing the exact problem. Well, good thing is that there're tons of info on the Web.

UPDATE2: Oops, I've read "WPF" instead of "WCF". Well, that makes your life much easier. You just need to add thread synchronisation to your WCF methods implementation and in most cases you can stop worrying about blocking them. So just carefully add locks to all relevant code...

like image 21
Dmitry Avatar answered Oct 24 '22 06:10

Dmitry


I agree with @Dmitry that using lock k (fun () -> ...) doesn't mean that you prevent mutation on k; it does mean that you acquire a key k to access an object. Because the key is unique, you have mutual exclusive access to the object to avoid getting wrong results.

Based on your example, running this below code in Debug mode results in results of 1, 3 or 6 for a arbitrarily. These values can be explained by the situation when one thread accessing the old value of a and the other thread trying to update that cell.

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;;

To ensure the correct result (which should be 3), you can introduce an object monitor, and any thread which would like to update a has to acquire monitor first:

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;;
like image 177
pad Avatar answered Oct 24 '22 06:10

pad