Task.WaitAll 的线程本地对象

发布于 2024-12-11 11:34:41 字数 1088 浏览 1 评论 0原文

我有少量任务,我想使用 Task.WaitAll 并行运行它们。这就是我现在所拥有的:

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)

问题是 Random 不是线程安全的(选择 Random 类仅用于说明目的)。如何为每个线程创建一个对象,而不是为每个任务创建一个对象,这样很浪费?

编辑:

像 Brian 和 Daniel 的建议一样使用 ThreadStatic 是一个很好的方法,特别是对于工厂类。不过,我更喜欢 Reed 和 Tomas 建议的 ThreadLocal,因为它看起来更简单。我同意使用主Random有点过于复杂。以下是我保留以供将来参考的混合解决方案:

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)

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

囚我心虐我身 2024-12-18 11:34:41

您可以将线程特定的状态放入工厂类中并将其标记为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

然后你可以这样做:

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)

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)
℡Ms空城旧梦 2024-12-18 11:34:41

您可以使用 ThreadLocal<'T> 来存储 Random 实例。这将提供一个线程本地随机值,可以在任务完成后将其释放。

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.

才能让你更想念 2024-12-18 11:34:41

StackOverflow 上最近的 F# 问题 完全实现了您的功能正在寻找。

使用线程本地(线程静态)存储是可行的方法,但您应该小心 - 当所有线程同时启动时,它们也有可能获得相同的初始种子。为了解决这个问题,上面的实现使用了一个受锁保护的 master Random,该锁用于为各个线程生成种子。

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.

友谊不毕业 2024-12-18 11:34:41

您可以将“rand”声明为 ThreadStatic。或者,TPL 中有一些允许 TLS 的 API;我不知道全部,但请参阅此处< /a>,可能是寻找其他人的起点。 (希望其他人能够发布更多细节或完善这个答案。)

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.)

紙鸢 2024-12-18 11:34:41

我认为您不需要本地线程。您可以使用当前线程 ID 在每个任务中创建新的 Random 对象。
例如:

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

我认为这比跟踪线程局部变量更简单、更容易理解。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文