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

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

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

我的查询

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

  1. for 循环中 i 的值因不同的实例而重复
  2. 对于那些 10K 迭代,我只创建了 8-9 个线程,而不是预期的 10k 线程。我期待 10K 线程弹出,完成各自的任务,然后优雅地消失。
  3. 我可以在我的项目中使用它作为类库,而不管平台是 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 $"Instance{i}. 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));

标签: c# multithreading async-await task-parallel-library .net-4.7.2


【解决方案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 线程上完成。要么通过一次调用将所有结果放入列表中,要么使用控制虚拟化之类的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-09
      • 2014-07-16
      • 2011-03-07
      • 2014-12-10
      • 2012-11-03
      相关资源
      最近更新 更多