Не дать запустить поток дважды

Если опустить нудные подробности, то суть в следующем.
Есть массив структур типа:


        public struct MyStruct
        {
            public string s1;
            public string s2;
			public MyThread thread = null;
        }

Есть таймер, который проходит по массиву и для каждой структуры проверяет некое условие. Если это условие выполняется и поток еще не запущен - запускаем его.
Как при завершении потока узнать, какой именно структуре он принадлежал, чтобы там занулить thread?
Пока на ум приходит только следующее:

        private void Thread_Complete(object sender)
        {
			пройтись по массиву и сравнить [b]sender[/b] с [b]thread[/b]
		}

Будет ли это работать? Если нет, то как сделать?

А что в MyThread и на какое событие подписан Thread_Complete?

Вообще в .NET куча новых более удобных средств для работы с потоками вместо обычного Thread. TPL/Task, async/await, … Ну или хотя бы BackgroundWorker.

Cогласен. Можно использовать его. Но сейчас не имеет значения, что использовать.
Главное это

Разве это зависит от того, как был создан поток и, тем более, что он делает? Мы же сейчас про момент его завершения говорим. Какая разница, что он делал?

Он каждые пару секунд должен качать JSON и проверять наличие в нем определенного параметра. Если параметр начинает существовать - сохраняем его в файл и завершаем цикл. А если сам JSON перестаёт существовать - тоже выходим.

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

Я просто спрашивал откуда это потому что в Thread вроде нет никаких событий завершения. И сам он ничего не синхронизирует, надо например вызывать Invoke / BeginInvoke, чтобы выполнить код в UI потоке (как Synchronize в Дельфи).

В BW есть, он похож на TThread из Дельфи. Данные там можно передать например через e.Result в DoWork и Completed.

Да, такая проблема может вообще не возникнуть с каким-нибудь async/await, или например с анонимными функциями (тогда не надо ничего искать, ссылка на структуру уже есть).

    struct Data
    {
        public int d;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var data = new Data { d = new Random().Next() };

        var thread = new Thread(() =>
        {
            Thread.Sleep(6000);

            Invoke((Action)(() =>
            {
                MessageBox.Show($"Hi! {data.d}");
            }));
        });
        thread.Start();
    }

Этот поток и не должен синхронизироваться. Он просто долго выполняется и выходит. А после выхода (завершения потока), нужно узнать, какой структуре в массиве он принадлежал, чтобы там занулить его идентификатор, чтобы была возможность запустить этот поток заново.
Допустим, если использовать классический метод создания потока. Когда поток создаётся в отдельном классе. Без лямба-функций и прочего.

В вашем коде создаётся новый экземпляр структуры. Не понял, зачем? Как тогда найти соответствие в массиве?

Тогда можно самому создать и вызвать.

Без новых так же.

        struct Data
        {
            public int d;
        }

        List<Data> list = new List<Data>
        {
            new Data { d = 42 },
            new Data { d = 43 },
        };

        private void button1_Click(object sender, EventArgs e)
        {
            int i = 0;
            foreach (var data in list)
            {
                int ind = ++i;
                var thread = new Thread(() =>
                {
                    Thread.Sleep(2000 * ind);

                    Invoke((Action)(() =>
                    {
                        MessageBox.Show($"Hi from thread #{ind}! {data.d}");
                    }));
                });
                thread.Start();
            }
        }

Тут тогда получится, что два потока меняют как минимум это

хотя присваивание ссылки вроде бы atomic в .NET, так что наверно ок (смотря что именно делать), но скорее всего стоит хотя бы добавить volatile. https://stackoverflow.com/a/5209632/964478

Вы не поняли. В каждой структуре есть поле public MyThread thread = null. Таймер проходит по массиву и если условие == true && thread == null, то thread = new MyThread(...);.
А сами потоки ни с массивом, ни с формой, ни друг с другом никак не будут взаимодействовать и ничего нигде менять не будут.
Просто надо узнать, какой именно поток завершился. Ведь при втором и последующих срабатываниях таймера, thread будет уже не null и поток уже больше никогда не запустится, даже если он уже завершился.

А сами потоки ни с массивом, ни с формой, ни друг с другом никак не будут взаимодействовать и ничего нигде менять не будут

а null кто поместит в структуру при завершении потока? Все равно сам поток и должен при завершении установить значение какой-то переменной в этой структуре, не важно в событии или еще как, важно, что используя синхронизацию

Вот я про это и говорю. В коде выше я не вижу или не понимаю, как и где это делается.

Так вот тут и будет поиск и изменение из другого потока если Thread_Complete просто

в конце выполнения потока.


А в цикле найти конечно можно, даже можно не писать его самому:

var item = list.FirstOrDefault(d => d.thread == sender);
if (item != null) ...

Тут кстати можно наткнуться на разницу в поведении классов и структур в .NET. Часто проще использовать class.

Из какого другого? Я планировал вызывать Thread_Complete в основном потоке через синхронизацию. А вы о чем?

что за разница в поведении?

То есть, вот эта фигня

                    Invoke((Action)(() =>
                    {
                        MessageBox.Show($"Hi from thread #{ind}! {data.d}");
                    }));

Выполнится при завершении потока и тут надо перебирать массив?

Если так, то да, всё в одном потоке.

Если имеется в виду типа такого:

                Thread thread;

                thread = new Thread(() =>
                {
                    ...

                    Invoke((Action)(() =>
                    {
                        Thread_Complete(thread);
                    }));
                });
                thread.Start();
            }

Где вызвано, там и выполнится. Код внутри выполнится в основном потоке.
Invoke будет ждать перед продолжением выполнения потока пока оно выполнится, с BeginInvoke сразу продолжит не дожидаясь.


структуры — значения, а не ссылки.

using System;
					
public class Program
{
	struct Data
	{
		public int v;
	}
	
	public static void Main()
	{
		Data d;
		d.v = 42;
		Console.WriteLine(d.v);
		
		var d2 = d;
		
		d.v = 43;
		
		Console.WriteLine(d.v); // 43
		Console.WriteLine(d2.v); // 42
	}
}

dotnetfiddle.net/X4cwHt

using System;
					
public class Program
{
	class Data
	{
		public int v;
	}
	
	public static void Main()
	{
		Data d = new Data();
		d.v = 42;
		Console.WriteLine(d.v);
		
		var d2 = d;
		
		d.v = 43;
		
		Console.WriteLine(d.v); // 43
		Console.WriteLine(d2.v); // 43
	}
}

dotnetfiddle.net/7N5a3Z

Весьма любопытно, но, по-моему, делать вот так

		Data d = new Data();
		d.v = 42;
		
		var d2 = d;

если Data это класс, это какой-то лютейший БДСМ. Зачем так делать с классами?

Ну это не только =, передача параметром в функцию, работа со списком, …

опять не понял. При чём тут var d2 = d?

При передаче параметром в функцию, доставании из списка и т.д. будет то же поведение — копия структуры, а не ссылка.

а если все передаваемые и доставаемые структуры были инициализированы через new?

То же самое, оно нужно только для вызова конструкторов.

А, всё, понял. Я не про то подумал.
Но тогда не понимаю, к чему было сказано это

если классы не копируются, то на что тут можно наткнуться?

Так наткнуться можно как раз если это структура, а не класс.

Как советуют по ссылке выше, структуры стоит использовать только для неизменяемых объектов (без изменения полей после инициализации), похожих по использованию на примитивные типы (числа, строки).

Тут явно не тот случай раз надо менять поле структуры.

Поэтому тут легко можно допустить ошибку типа случайной передачи параметром и изменения копии, присвоения элемента массива/списка в переменную и т.д.

using System;
					
public class Program
{
	struct Data
	{
		public int v;
	}
	
	public static void Main()
	{
		var arr = new Data[2];
		
		foreach (var it in arr)
		{
			//it.v = 42;  // ошибка компиляции
		}
		
		var item = arr[0];
		item.v = 42;
		
		Console.WriteLine(arr[0].v); // 0