Создание одноэлементного объекта в нескольких разных потоках с использованием TPL - PullRequest
1 голос
/ 21 июня 2020

Это просто ситуация самообучения. Я новичок в TPL и Threading. В любом случае, я использую общий c Singleton-класс и создаю ~ 10K экземпляров, чтобы каждый раз проверять, возвращает ли мой код тот же экземпляр или каждый раз создает новый экземпляр. Я создаю экземпляры асинхронно, используя Task Factory внутри for l oop. Чтобы проверить создание экземпляра, я возвращаю строку с этой информацией в виде списка строк:

  1. Счетчик итераций
  2. Имя экземпляра
  3. Хэш-код экземпляра и
  4. ThreadId и отображение списка строк в списке.

Мои запросы

При запуске я обнаружил несколько вещей,

  1. значение i внутри for l oop дублируется для разных intances
  2. для этих 10K итераций, у меня создано только 8-9 потоков вместо ожидаемых 10k потоков. Я ожидал, что 10K потоков будут всплывать, выполнять свою индивидуальную задачу и затем плавно исчезать. *

Пожалуйста, оставьте заметку в моих мыслях по теме 1OK :). Хорошая / плохая идея для многопоточности?

Мой код

Singleton Class

public sealed class Singleton<T> where T : class
{
    static Singleton() {  }
    private Singleton() { }
    public static T Instance { get; } = Activator.CreateInstance<T>();
}

Class: 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}";
    }

}

Frontend _ На стороне пользовательского интерфейса событие On 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 элементами. Пожалуйста, помогите мне заполнить его асинхронным способом. Я знал bgworker, begininvoke и methodinvoker. Есть ли что-нибудь кроме тоже в TPL ??

Output

введите описание изображения здесь

---

Обновление

Как было предложено, если я использую Parallel.For, тогда вместо строк 10K я получаю случайное число 9491, 9326 и др. c. Т.е. меньше 10К. Я не знаю, почему ????

Вот мой обновленный код для метода LoopAsyn c с использованием Parallel.For

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

Ответы [ 2 ]

0 голосов
/ 23 июня 2020

значение i внутри for l oop дублируется для разных значений

Это не имеет ничего общего с потоками / параллельными / асинхронными или одноэлементными экземплярами . Вы видите это, потому что замыкания захватывают переменные, а не значения. Итак, этот код:

for (int i = 0; i <= 9999; i++)
{
  list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(i)));
}

передает переменную i в закрытие () => CreateAndLogInstances(i), а не текущее значение из i. Чтобы зафиксировать текущее значение и использовать его в вашем закрытии, вам понадобится отдельная переменная для каждого закрытия, как рекомендуется в комментарии:

for (int i = 0; i <= 9999; i++)
{
  var index = i;
  list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(index)));
}

для этих 10K итераций, у меня только 8-9 создано потоков вместо ожидаемых 10 тыс. потоков. Я ожидал, что 10K потоков будут всплывать, выполнять свою индивидуальную задачу и затем плавно исчезать.

Нет, вам бы очень не хотелось, чтобы это произошло. Создание и уничтожение потоков связано с большими накладными расходами. Очереди StartNew и Parallel работают с пулом потоков, и пул потоков будет быстро расти до определенной точки, а затем намеренно медленно расти. Это связано с тем, что, например, на 8-ядерной машине нет смысла иметь 10 тыс. Потоков, потому что они все равно не могут выполняться.

Могу ли я использовать это в своих проектах как библиотеки классов, независимо от платформ - Web, Windows или Mobile?

Я никогда не рекомендую использовать параллельную обработку в веб-приложениях, потому что ваш веб-хост уже распараллелил ваши запросы. Таким образом, дополнительная параллельная обработка имеет тенденцию обременять ваш веб-сервер и потенциально делает его гораздо менее отзывчивым к другим запросам. Пожалуйста, помогите мне заполнить его асинхронным способом.

Обычно вы не хотите делать 10k обновлений пользовательского интерфейса практически одновременно. Параллельная обработка не помогает с пользовательским интерфейсом, потому что все обновления пользовательского интерфейса должны выполняться в потоке пользовательского интерфейса. Либо поместите все результаты в список одним вызовом, либо используйте что-то вроде виртуализации управления.

0 голосов
/ 22 июня 2020

Добавление одного и того же объекта в список 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(), который также является одинаковым в каждом случае, что указывает на высокую вероятность того, что вы имеете дело только с одним экземпляром.

...