使用 TPL 在多个不同线程上创建单例对象

Posted

技术标签:

【中文标题】使用 TPL 在多个不同线程上创建单例对象【英文标题】:Singleton object creation on multiple different threads using TPL 【发布时间】:2020-10-11 11:14:22 【问题描述】:

这只是一个自我学习的情况。我对 TPL 和线程很陌生。无论如何,我正在使用一个通用的 Singleton 类并创建 ~10K 实例来检查我的代码是否每次都返回相同的实例或创建新实例。我在 for 循环中使用任务工厂异步创建实例。为了验证实例的创建,我返回了一个字符串,其中包含这些信息作为字符串列表:

    迭代计数器 实例名称 实例的哈希码和 线程 ID 并将字符串列表显示到列表框。

我的查询

在跑步中,我发现了一些东西,

    for 循环中 i 的值因不同的实例而重复 对于那些 10K 迭代,我只创建了 8-9 个线程,而不是预期的 10k 线程。我期待 10K 线程弹出,完成各自的任务,然后优雅地消失。 我可以在我的项目中使用它作为类库,而不管平台是 Web、Windows 还是移动?

请在我的 1OK 线程想法上留言 :)。在多线程方面是好主意还是坏主意?

我的代码

单例类

public sealed class Singleton<T> where T : class

    static Singleton()   
    private Singleton()  
    public static T Instance  get;  = Activator.CreateInstance<T>();

类:SingletonInThread

public class SingletonInThread


    /// <summary>
    /// Method responsible for creation of same instance, 10K times using Task.Factory.StartNew()
    /// </summary>
    /// <returns></returns>
    public async Task<IEnumerable<string>> LoopAsync()
    
        List<Task<string>> list = new List<Task<string>>();
        for (int i = 0; i <= 9999; i++)
        
            list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(i)));
        
       return  await Task.WhenAll<string>(list);
    

    /// <summary>
    /// Creates new instance of Logger and logs its creation with few details. Kind of Unit of Work.
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    private string CreateAndLogInstances(int i)
    
        var instance = Singleton<Logger>.Instance;
        return $"Instancei. Name of instance= instance.ToString() && Hashcode =instance.GetHashCode() && ThreadId= Thread.CurrentThread.ManagedThreadId";
    


前端 _ 在 UI 方面,在 buttonclick 事件中,填充列表框

private async void button1_Click(object sender, EventArgs e)

    IEnumerable<string> list = await new SingletonInThread().LoopAsync();

    foreach (var item in list)
    
        listBox1.Items.Add(item);

    

另外,我注意到在用 10K 项填充列表框时,我的 UI 被阻塞了。请帮我以异步方式填充它。我认识 bgworker、begininvoke 和 methodinvoker。 TPL里除了too还有什么??

输出

---

更新

按照建议,如果我使用 Parallel.For,那么,我得到的不是 10K 字符串,而是 9491、9326 等的随机数字。即小于 10K。不知道为什么???

这是我使用 Parallel.For 的 LoopAsync 方法的更新代码

public  IEnumerable<string> LoopAsync()

     List<string> list = new List<string>();
           
     Parallel.For(0, 9999, i =>
     
          list.Add( CreateAndLogInstances(i));
     );
     return list;

【问题讨论】:

必读:csharpindepth.com/Articles/Singleton。此外,使用 async/await 进行并行处理是一个非常奇怪的选择,我建议您改用 Parallel.For 和 Parallel.ForEach 方法 @CamiloTerevinto 请看一看。仍然存在问题 你需要捕获i:var index = i; list.Add( CreateAndLogInstances(index)); 【参考方案1】:

将同一个对象多次添加到 WinForms 列表框中会导致列表框中出现多行,例如:

    private void Form1_Load(object sender, EventArgs e)
    
        string foo = "Hello, world";
        listBox1.Items.Add(foo);
        listBox1.Items.Add(foo);
        listBox1.Items.Add(foo);
    

产生三行声明Hello, world。因此,在您的示例中收到 10,000 行也就不足为奇了。但它们是同一个对象,还是您创建了多个对象?

我创建了自己的 Logger 类:

public class Logger

    static private Random rnd = new Random();
    public int Id  get;  = rnd.Next();
    public override string ToString()
    
        return Id.ToString();
    

确实,每个输出行都有相同的Id,因此表明在每种情况下都使用了相同的对象实例。您还将调用输出到GetHashCode(),这在每种情况下也是相同的,表明您很可能只处理一个实例。

【讨论】:

【参考方案2】:

for 循环中 i 的值因不同的实例而重复

这与线程/并行/异步或单例实例无关。您会看到这一点,因为闭包捕获变量,而不是值。所以这段代码:

for (int i = 0; i  CreateAndLogInstances(i)));

变量 i 传递给闭包() =&gt; CreateAndLogInstances(i),而不是i 的当前。要捕获当前值并在您的闭包中使用它,您需要每个闭包一个单独的变量,如评论中所建议的那样:

for (int i = 0; i  CreateAndLogInstances(index)));

对于这 10K 次迭代,我只创建了 8-9 个线程,而不是预期的 10k 线程。我期待 10K 线程弹出,完成各自的任务,然后优雅地消失。

不,您非常不希望这种情况发生。线程的创建和销毁有很多开销。 StartNewParallel 队列工作到线程池,线程池会快速增长到某个点然后缓慢增长,这是故意的。这是因为,例如,在一台 8 核机器上,拥有 10k 个线程是没有意义的,因为它们无论如何都不能全部运行。

我可以在我的项目中使用它作为类库,而不管平台是 Web、Windows 还是移动?

我从不建议在网络应用程序上使用并行处理,因为您的网络主机已经并行化了您的请求。因此,进行额外的并行处理往往会给您的网络服务器带来负担,并可能使其对其他请求的响应能力大大降低。

另外,我注意到在用 10K 项填充列表框时,我的 UI 被阻塞了。请帮我以异步方式填充它。

您通常希望避免几乎同时进行 10k 次 UI 更新。并行处理对 UI 没有帮助,因为所有 UI 更新都必须在 UI 线程上完成。要么通过一次调用将所有结果放入列表中,要么使用控制虚拟化之类的东西。

【讨论】:

以上是关于使用 TPL 在多个不同线程上创建单例对象的主要内容,如果未能解决你的问题,请参考以下文章

OKHTTP Singleton 对象在多线程系统中处理不同的 API 调用

DCL_单例模式

如何创建线程?如何保证线程安全?

浅淡java单例模式结合多线程测试

单例模式

Windows 模拟和 TPL