pad
pad

Reputation: 41290

Thread-local objects for Task.WaitAll

I have a small number of tasks, and I want to use Task.WaitAll to run them in parallel. This is what I have by now:

type System.Threading.Tasks.Task with
  static member WaitAll(ts) =
    Task.WaitAll [| for t in ts -> t :> Task |]

let processf p fs =
  let rand = Random ()
  let ts = fs |> Array.map (fun f -> Task.Factory.StartNew(fun () -> p(f, rand)))
  Task.WaitAll(ts)
  ts |> Array.map (fun t -> t.Result)

The problem is that Random is not thread-safe (Random class is chosen for illustration purpose only). How can I create an object for each thread rather than create an object for each Task, which is wasteful?

EDIT:

Using ThreadStatic like Brian's and Daniel's suggestions is a good approach, especially with a factory class. However, I prefer ThreadLocal suggested by Reed and Tomas because it looks simpler. I agree that using a master Random is a bit over-complex. The following is a mixed solution I keep for future reference:

let processf p fs =
  use localRand = new ThreadLocal<_>(
                       fun() -> Random(Thread.CurrentThread.ManagedThreadId))
  let ts = fs |> Array.map (fun f -> 
                          Task.Factory.StartNew(fun () -> p(f, localRand.Value)))
  Task.WaitAll(ts)
  ts |> Array.map (fun t -> t.Result)

Upvotes: 2

Views: 471

Answers (5)

Ankur
Ankur

Reputation: 33657

I don't think you event need Thread Local. You can create the new Random object in each task using the current thread ID. Ex:

Task.Factory.StartNew(fun () -> p(f, new Random(Thread.CurrentThread.ManagedThreadId))))

Which I think is much more simple and easy to understand rather than tracking the Thread local variable.

Upvotes: 0

Daniel
Daniel

Reputation: 47914

You could put your thread-specific state in a factory class and mark it ThreadStatic.

type PerThread =

  [<ThreadStatic; DefaultValue>]
  static val mutable private random : Random

  static member Random = 
    match PerThread.random with
    | null -> PerThread.random <- Random(Thread.CurrentThread.ManagedThreadId)
    | _ -> ()
    PerThread.random

Then you can do:

let process p fs =
  let ts = fs |> Array.map (fun f -> 
    Task.Factory.StartNew(fun () -> p(f, PerThread.Random)))
  Task.WaitAll(ts)
  ts |> Array.map (fun (t: Task) -> t.Result)

Upvotes: 3

Tomas Petricek
Tomas Petricek

Reputation: 243096

A recent F# question here on StackOverflow implements exactly what you're looking for.

Using thread-local (thread-static) storage is the way to go, but you should be careful - when all threads start at the same time, there is a chance that they'll also get the same initial seed. To solve this problem, the implementation above uses a master Random protected by a lock that is used to generate seeds for individual threads.

Upvotes: 2

Reed Copsey
Reed Copsey

Reputation: 564631

You can use the ThreadLocal<'T> class to store the Random instance. This will provide a thread-local random value, which can be disposed of upon completion of your tasks.

Upvotes: 2

Brian
Brian

Reputation: 118895

You could declare 'rand' as a ThreadStatic. Alternatively, there are some APIs in the TPL that allow for TLS; I don't know them all, but see e.g. here, may be jumping off point to find others. (Hopefully others will post more details or refine this answer.)

Upvotes: 1

Related Questions