Reputation: 41290
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
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
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
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
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
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