Отдать значения в поток и вернуть результат выполнения?
Здраствуйте, впервые работаю с потоками. У меня вопрос прост, но я его не могу найти в интернете.
Как передать массив int[] в поток, где выполнится некоторый код, после выполнения поток должен вернуть другой массив.
Как это сделать ?
И как при необходимости завершить поток из другого места, условно из другого потока.
Заранее благодарю за ответ.
- Вопрос задан более года назад
- 350 просмотров
Комментировать
Решения вопроса 1

Василий Банников @vabka Куратор тега C#
Токсичный шарпист
1. Сначала убедись, что для твоей задачи действительно нужен именно поток, а не какая-то более высокоуровневая абстракция, например Task
2. Если всё-таки поток, то есть два варианта:
1. Через обратный вызов. В конце своей работы, в потоке нужно вызвать какой-то метод, который будет обрабатывать результат.
using System.Threading; var data = new int[1]; // Какие-то данные var thread = new Thread(() => < var result = data.Length; // Какие-то ужасно сложные вычисления HandleResult(result); // Это можно передать как параметр-делегат, но тут мы будем конкретный метод использовать >); thread.Start(); // Стартуем // Какие-то дела thread.Join(); // Всё равно надо по-хорошему дождаться окончания работы потока void HandleResult(int value)
2. Через Join и какую-то общую переменную или поле.
using System.Threading; var data = new int[1]; // Какие-то данные var result = 0; // Какой-то результат (инициализируем значением по-умолчанию) var thread = new Thread(() => < result = data.Length; // Какие-то ужасно сложные вычисления >); thread.Start(); // Стартуем // Какая-то работа thread.Join(); // Дожидаемся окончания работы потока Console.WriteLine(result); // Используем результат работы
Ответ написан более года назад
Комментировать
Нравится 1 Комментировать
Ответы на вопрос 2

Никак, просто обращайтесь из одного потока к другому напрямую или через Invoke. Если напрямую и боитесь что доступ может производится в один момент из двух потоков, используйте lock перед записью.
Для отмены реализуйте механизм CancellationToken
Ответ написан более года назад
Комментировать
Нравится Комментировать

Участник OpenStreetMap
В такс можно передать объект. А объектом может выступать что угодно. Соответственно это и будет вашей точкой обмена, вы туда кладёте исходные данные, такс результаты. Через cancellationTokenSource можно завершить таск преждевременно. Ну а Sync используется если у вас несколько поток и им необходимо синхронизироваться межу собой или например вы хотите забрать промежуточные результаты.
public class TaskState < public CancellationToken CancellationToken; public int[] Input; public int[] Output; public object Sync; >private static void TaskBodyPoolQueue(object state) < var taskState = (TaskState)state; do < // Calculate >while (!taskState.CancellationToken.IsCancellationRequested); taskState.Output = . > taskStateProgress = new TaskState() < Input = . CancellationToken = cancellationTokenSource.Token, Sync = new object() >; for (int i = 0; i < 6; i++) ThreadPool.QueueUserWorkItem(new WaitCallback(TaskBodyPoolQueue), taskStateProgress);
Как передать данные в поток thread c
В предыдущей статье было рассмотрено, как запускать в отдельных потоках методы без параметров. А что, если нам надо передать какие-нибудь параметры в поток?
Для этой цели используется делегат ParameterizedThreadStart , который передается в конструктор класса Thread:
public delegate void ParameterizedThreadStart(object? obj);
Применение делегата ParameterizedThreadStart во многом похоже на работу с ThreadStart . Рассмотрим на примере:
using System.Threading; // создаем новые потоки Thread myThread1 = new Thread(new ParameterizedThreadStart(Print)); Thread myThread2 = new Thread(Print); Thread myThread3 = new Thread(message => Console.WriteLine(message)); // запускаем потоки myThread1.Start("Hello"); myThread2.Start("Привет"); myThread3.Start("Salut"); void Print(object? message) => Console.WriteLine(message);
При создании потока в конструктор класса Thread передается объект делегата ParameterizedThreadStart new Thread(new ParameterizedThreadStart(Print)) , либо непосредственно метод, который соответствует этому делегату ( new Thread(Print) ), в том числе в виде лямбда-выражения ( new Thread(message => Console.WriteLine(message)) )
Затем при запуске потока в метод Start() передается значение, которое передается параметру метода Print. И в данном случае мы получим следующий консольный вывод:
Salut Hello Привет
При использовании ParameterizedThreadStart мы сталкиваемся с ограничением: мы можем запускать во втором потоке только такой метод, который в качестве единственного параметра принимает объект типа object? . Поэтому если мы хотим использовать данные других типов, в самом методе необходимо выполнить приведение типов. Например:
using System.Threading; int number = 4; // создаем новый поток Thread myThread = new Thread(Print); myThread.Start(number); // n * n = 16 // действия, выполняемые во втором потокке void Print(object? obj) < // здесь мы ожидаем получить число if (obj is int n) < Console.WriteLine($"n * n = "); > >
в данном случае нам надо дополнительно привести переданное значение к типу int, чтобы его использовать в вычислениях.
Но что делать, если нам надо передать не один, а несколько параметров различного типа? В этом случае можно определить свои типы:
using System.Threading; Person tom = new Person("Tom", 37); // создаем новый поток Thread myThread = new Thread(Print); myThread.Start(tom); void Print(object? obj) < // здесь мы ожидаем получить объект Person if (obj is Person person) < Console.WriteLine($"Name = "); Console.WriteLine($"Age = "); > > record class Person(string Name, int Age);
Сначала определяем специальный класс Person, объект которого будет передаваться во второй поток, а в методе Main передаем его во второй поток.
Но тут опять же есть одно ограничение: метод Thread.Start не является типобезопасным, то есть мы можем передать в него любой тип, и потом нам придется приводить переданный объект к нужному нам типу. Для решения данной проблемы рекомендуется объявлять все используемые методы и переменные в специальном классе, а в основной программе запускать поток через ThreadStart. Например:
using System.Threading; Person tom = new Person("Tom", 37); // создаем новый поток Thread myThread = new Thread(tom.Print); myThread.Start(); record class Person(string Name, int Age) < public void Print() < Console.WriteLine($"Name = "); Console.WriteLine($"Age = "); > >
Создание и ожидание выполнения потоков
Теги: pthreads, pthread_create, pthread_join, EINVAL, ESRCH, EDEADLK, EDEADLOCK, EAGAIN, EPERM, PTHREAD_THREADS_MAX, передача аргументов потоку, возврат аргументов из потока, ошибки pthread_create, ошибки pthread_join, ожидание потока, объединение потоков, идентификатор потока, пример pthreads.
Создание и ожидание потока
Р ассмотрим простой пример
#include #include #include #include #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define SUCCESS 0 void* helloWorld(void *args) < printf("Hello from thread!\n"); return SUCCESS; >int main() < pthread_t thread; int status; int status_addr; status = pthread_create(&thread, NULL, helloWorld, NULL); if (status != 0) < printf("main error: can't create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); >printf("Hello from main!\n"); status = pthread_join(thread, (void**)&status_addr); if (status != SUCCESS) < printf("main error: can't join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); >printf("joined with address %d\n", status_addr); _getch(); return 0; >
В данном примере внутри основного потока, в котором работает функция main, создаётся новый поток, внутри которого вызывается функция helloWorld. Функция helloWorld выводит на дисплей приветствие. Внутри основного потока также выводится приветствие. Далее потоки объединяются.
Новый поток создаётся с помощью функции pthread_create
int pthread_create(*ptherad_t, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg);
Функция получает в качестве аргументов указатель на поток, переменную типа pthread_t, в которую, в случае удачного завершения сохраняет id потока. pthread_attr_t – атрибуты потока. В случае если используются атрибуты по умолчанию, то можно передавать NULL. start_routin – это непосредственно та функция, которая будет выполняться в новом потоке. arg – это аргументы, которые будут переданы функции.
Поток может выполнять много разных дел и получать разные аргументы. Для этого функция, которая будет запущена в новом потоке, принимает аргумент типа void*. За счёт этого можно обернуть все передаваемые аргументы в структуру. Возвращать значение можно также через передаваемый аргумент.
В случае успешного выполнения функция возвращает 0. Если произошли ошибки, то могут быть возвращены следующие значения
- EAGAIN – у системы нет ресурсов для создания нового потока, или система не может больше создавать потоков, так как количество потоков превысило значение PTHREAD_THREADS_MAX (например, на одной из машин, которые используются для тестирования, это магическое число равно 2019)
- EINVAL – неправильные атрибуты потока (переданные аргументом attr)
- EPERM – Вызывающий поток не имеет должных прав для того, чтобы задать нужные параметры или политики планировщика.
Пройдём по программе
#define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define SUCCESS 0
Здесь мы задаём набор значений, необходимый для обработки возможных ошибок.
void* helloWorld(void *args)
Это функция, которая будет работать в отдельном потоке. Она не будет получать никаких аргументов. По стандарту считается, что явный выход из функции вызывает функцию pthread_exit, а возвращаемое значение будет передано при вызове функции pthread_join, как статус.
status = pthread_create(&thread, NULL, helloWorld, NULL); if (status != 0)
Здесь создаётся и сразу же исполняется новый поток. Поток не получает никаких атрибутов или аргументов. После создания потока происходит проверка на ошибку.
status = pthread_join(thread, (void**)&status_addr); if (status != SUCCESS)
Приводит к тому, что основной поток будет ждать завершения порождённого. Функция
int pthread_join(pthread_t thread, void **value_ptr);
Откладывает выполнение вызывающего (эту функцию) потока, до тех пор, пока не будет выполнен поток thread. Когда pthread_join выполнилась успешно, то она возвращает 0. Если поток явно вернул значение (это то самое значение SUCCESS, из нашей функции), то оно будет помещено в переменную value_ptr. Возможные ошибки, которые возвращает pthread_join
- EINVAL – thread указывает на не объединяемый поток
- ESRCH – не существует потока с таким идентификатором, который хранит переменная thread
- EDEADLK – был обнаружен дедлок (взаимная блокировка), или же в качестве объединяемого потока указан сам вызывающий поток.
Пример создания потоков с передачей им аргументов
П усть мы хотим передать потоку данные и вернуть что-нибудь обратно. Скажем, передавать потоку будем строку, а возвращать из потока длину этой строки.
Так как функция может получать только указатель типа void, то все аргументы следует упаковать в структуру. Определим новый тип структуру:
typedef struct someArgs_tag < int id; const char *msg; int out; >someArgs_t;
Здесь id – это идентификатор потока (он в общем-то не нужен в нашем примере), второе поле это строка, а третье длина строки, которую мы будем возвращать.
Внутри функции приводим аргумент к нужному типу, выводим на печать строку и засовываем в структуру обратно вычисленную длину строки.
void* helloWorld(void *args) < someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) < return BAD_MESSAGE; >len = strlen(arg->msg); printf("%s\n", arg->msg); arg->out = len; return SUCCESS; >
В том случае, если всё прошло удачно, то в качестве статуса возвращаем значение SUCCESS, а если была допущена ошибка (в нашем случае, если передана нулевая строка), то выходим со статусом BAD_MESSAGE.
В этом примере создадим 4 потока. Для 4-х потоков понадобятся массив типа pthread_t длинной 4, массив передаваемых аргументов и 4 строки, которые мы и будем передавать.
pthread_t threads[NUM_THREADS]; int status; int i; int status_addr; someArgs_t args[NUM_THREADS]; const char *messages[] = < "First", NULL, "Third Message", "Fourth Message" >;
Первым делом заполняем значения аргументов.
for (i = 0; i
Далее создаём в цикле новые потоки
for (i = 0; i < NUM_THREADS; i++) < status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) < printf("main error: can't create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); >>
Затем ждём завершения
for (i = 0; i < NUM_THREADS; i++) < status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) < printf("main error: can't join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); >printf("joined with address %d\n", status_addr); >
Под конец ещё выводим аргументы, которые теперь хранят возвращённые значения. Заметьте, что один из аргументов «плохой» (строка равна NULL). Вот полный код
#include #include #include #include #include #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define BAD_MESSAGE -13 #define SUCCESS 0 typedef struct someArgs_tag < int id; const char *msg; int out; >someArgs_t; void* helloWorld(void *args) < someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) < return BAD_MESSAGE; >len = strlen(arg->msg); printf("%s\n", arg->msg); arg->out = len; return SUCCESS; > #define NUM_THREADS 4 int main() < pthread_t threads[NUM_THREADS]; int status; int i; int status_addr; someArgs_t args[NUM_THREADS]; const char *messages[] = < "First", NULL, "Third Message", "Fourth Message" >; for (i = 0; i < NUM_THREADS; i++) < args[i].id = i; args[i].msg = messages[i]; >for (i = 0; i < NUM_THREADS; i++) < status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) < printf("main error: can't create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); >> printf("Main Message\n"); for (i = 0; i < NUM_THREADS; i++) < status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) < printf("main error: can't join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); >printf("joined with address %d\n", status_addr); > for (i = 0; i < NUM_THREADS; i++) < printf("thread %d arg.out = %d\n", i, args[i].out); >_getch(); return 0; >
Выполните его несколько раз. Заметьте, что порядок выполнения потоков не детерминирован. Запуская программу, можно каждый раз получить другой порядок выполнения.
ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

Всё ещё не понятно? – пиши вопросы на ящик
С++11 std::thread и передача переменных по ссылке
Для случая Visual Studio 2013 вариант с амперсандом (&) не приводит к ошибке компиляции, но при этом переменная передается по значению, как и в случае без амперсанда. Вариант с враппером std::ref( ) работает корректно, как и предсказано спецификацией С++2011.
Писать в Visual Studio лично мне несравненно удобнее, чем в редакторе vi, поэтому, конечно же, я пишу изначально на VC++2013. Однако при переносе в Linux/gcc4.8.2, сломал весь мозг, что означают указанные выше ошибки.
И ни один выкинутый Гуглем пост мне не посоветовал просто: "Перестань пытаться использовать ссылки в функции для потока std::theread! Используй старые добрые указатели!"
В общем, независимо от того, баг это или фича, - не используйте ссылки при передачи параметров в функции потоков std::theread, используйте указатели!