2010-11-16 2 views

ответ

69

Это зависит от того, какую версию .NET Framework вы используете. .NET 4.0 делает управление потоками намного проще с помощью задач:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Task task1 = Task.Factory.StartNew(() => doStuff()); 
     Task task2 = Task.Factory.StartNew(() => doStuff()); 
     Task task3 = Task.Factory.StartNew(() => doStuff()); 

     Task.WaitAll(task1, task2, task3); 
       Console.WriteLine("All threads complete"); 
    } 

    static void doStuff() 
    { 
     //do stuff here 
    } 
} 

В предыдущих версиях .NET вы можете использовать BackgroundWorker объект, используйте ThreadPool.QueueUserWorkItem(), или создавать свои темы вручную и использовать Thread.Join() ждать их, чтобы завершить :

static void Main(string[] args) 
{ 
    Thread t1 = new Thread(doStuff); 
    t1.Start(); 

    Thread t2 = new Thread(doStuff); 
    t2.Start(); 

    Thread t3 = new Thread(doStuff); 
    t3.Start(); 

    t1.Join(); 
    t2.Join(); 
    t3.Join(); 

    Console.WriteLine("All threads complete"); 
} 
+3

API-интерфейс задач на сегодняшний день является самым простым решением. – JefClaes

+0

прост и понятен..thanks :) –

+0

Есть некоторые ограничения, о которых нужно знать - если вам требуется, чтобы поток имел определенный приоритет, то вы не можете использовать Задачи. Ну, технически вы могли бы, но это плохая идея, чтобы изменить приоритет потока потока внутри Task, потому что поток принадлежит файлу threadpool, и поэтому ваш пользовательский приоритет может повлиять на некоторый другой код. – JustAMartin

4

В .Net 4.0 вы можете использовать Task Parallel Library.

В более ранних версиях вы можете создать список объектов Thread в цикле, вызывая Start на каждом из них, затем выполните еще один цикл и вызовите Join на каждом из них.

+0

зачем нужен еще один цикл? – user496949

+2

@user: Если вы вызываете 'Join' сразу после запуска потока, вы в конечном итоге ожидаете его завершения до начала любых других потоков. Вам нужно запустить _все_ потоков, ** затем ** «Присоединить» все из них. – SLaks

+0

вы абсолютно правы @SLaks :) –

21

Думаю, что вам нужно WaitHandler.WaitAll. Вот пример:

public static void Main(string[] args) 
{ 
    int numOfThreads = 10; 
    WaitHandle[] waitHandles = new WaitHandle[numOfThreads]; 

    for (int i = 0; i < numOfThreads; i++) 
    { 
     var j = i; 
     // Or you can use AutoResetEvent/ManualResetEvent 
     var handle = new EventWaitHandle(false, EventResetMode.ManualReset); 
     var thread = new Thread(() => 
           { 
            Thread.Sleep(j * 1000); 
            Console.WriteLine("Thread{0} exits", j); 
            handle.Set(); 
           }); 
     waitHandles[j] = handle; 
     thread.Start(); 
    } 
    WaitHandle.WaitAll(waitHandles); 
    Console.WriteLine("Main thread exits"); 
    Console.Read(); 
} 

EDIT FCL имеет несколько более удобных функций.

(1) Task.WaitAll, а также его перегрузки, когда вы хотите выполнять некоторые задачи параллельно (и без возвращаемых значений).

var tasks = new[] 
{ 
    Task.Factory.StartNew(() => DoSomething1()), 
    Task.Factory.StartNew(() => DoSomething2()), 
    Task.Factory.StartNew(() => DoSomething3()) 
};  
Task.WaitAll(tasks); 

(2) Task.WhenAll, когда вы хотите сделать некоторые задачи с возвращаемые значения, он выполняет операции, и помещает результаты в массив. Это поточно-безопасный, вам не нужно использовать поточно-безопасный контейнер и реализовать операцию добавления самостоятельно.

var tasks = new[] 
{ 
    Task.Factory.StartNew(() => GetSomething1()), 
    Task.Factory.StartNew(() => GetSomething2()), 
    Task.Factory.StartNew(() => GetSomething3()) 
};  
var things = Task.WhenAll(tasks); 
+0

@Kirk: Я собирался добавить пример только сейчас, но должен был пойти на собрание. –

+0

приятно! +1 (и удаленный комментарий) –

+0

хорошее объяснение для значений «нет возврата» и «возврата» из метода! –

0

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

private object _lock=new object(); 
private int _runningThreads = 0; 

private int Counter{ 
    get{ 
      lock(_lock) 
       return _runningThreads; 
    } 
    set{ 
      lock(_lock) 
       _runningThreads = value; 
    } 
} 

Теперь, когда вы создаете рабочий поток, увеличить счетчик:

var t=new BackgroundWorker(); 
//ADD RunWorkerCompleted HANDLER 
//START THREAD 
Counter++; 

В работе завершена, уменьшает счетчик:

private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    Counter--; 
} 

Теперь вы можете проверить для счетчика в любое время, чтобы см., если какая-либо нить работает:

if(Couonter>0){ 
    //SOME THREAD IS YET TO FINISH. 
} 
+1

Вам не нужен замок. На самом деле, поскольку вы только записываете форму свойства в поток пользовательского интерфейса, вам ничего не нужно. – SLaks

+2

Возможно, я ошибаюсь, но я думаю, что замок правильный; он уменьшает счетчик в обработчике событий, который запускается, когда каждый рабочий завершает работу. –

+2

@Mark: событие Completed всегда запускается в потоке пользовательского интерфейса. Кроме того, замок не будет иметь никакого эффекта; Чтение и запись Int32 являются атомарными. Если бы были проблемы с потоком, блокировка не помогла бы; ему нужно будет вызвать 'Interlocked.Increment'. – SLaks

4

Я сделал очень простой метод расширения, чтобы ждать все нити коллекции:

using System.Collections.Generic; 
using System.Threading; 
namespace Extensions 
{ 
    public static class ThreadExtension 
    { 
     public static void WaitAll(this IEnumerable<Thread> threads) 
     { 
      if(threads!=null) 
      { 
       foreach(Thread thread in threads) 
       { thread.Join(); } 
      } 
     } 
    } 
} 

Тогда вы просто звоните:

List<Thread> threads=new List<Thread>(); 
//Add your threads to this collection 
threads.WaitAll(); 
+0

Я бы предпочел использовать 'ThreadHelpers.WaitAll (threadCollection)' .. в любом случае, это во многом то, что я использую для тестов. Мне редко приходилось «ждать все» в реальном коде. – user2864740

0

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

//'arrRunningThreads' = List<Thread> 

//Wait for all threads 
const int knmsMaxWait = 3 * 1000;   //3 sec timeout 
int nmsBeginTicks = Environment.TickCount; 
foreach(Thread thrd in arrRunningThreads) 
{ 
    //See time left 
    int nmsElapsed = Environment.TickCount - nmsBeginTicks; 
    int nmsRemain = knmsMaxWait - nmsElapsed; 
    if(nmsRemain < 0) 
     nmsRemain = 0; 

    //Then wait for thread to exit 
    if(!thrd.Join(nmsRemain)) 
    { 
     //It didn't exit in time, terminate it 
     thrd.Abort(); 

     //Issue a debugger warning 
     Debug.Assert(false, "Terminated thread"); 
    } 
} 
+0

Не отвечает на вопрос. В любом случае, почему бы вам просто не использовать Task.Wait (timeout)? –

0

Если вы не хотите использовать класс задач (например, в .NET 3.5), вы можете просто начать все ваши темы, а затем добавить их в список и присоединиться к ним в цикле Еогеасп.

Пример:

List<Thread> threads = new List<Thread>(); 


// Start threads 
for(int i = 0; i<10; i++) 
{ 
    int tmp = i; // copy value for closure 
    Thread t = new Thread(() => Console.WriteLine(tmp)); 
    t.Start; 
    threads.Add(t); 
} 

// Await threads 
foreach(Thread thread in threads) 
{ 
    thread.Join(); 
} 
0

В моем случае я не мог создать экземпляр свои объекты на пуле потоков с Task.Run() или Task.Factory.StartNew(). Они не синхронизировали моих давно работающих делегатов правильно. Мне нужно, чтобы делегаты выполняли асинхронно, приостанавливая мой основной поток для их коллективного завершения. Thread.Join() не будет работать, так как я хотел дождаться коллективного завершения в середине родительского потока, а не в конце. С Task.Run() или Task.Factory.StartNew() либо все дочерние потоки заблокированы друг от друга, либо родительский поток не будет заблокирован ... Я не мог понять, как идти с делегатами async из-за повторной сериализации синтаксиса await.

Вот мое решение, используя темы вместо задачи:

using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.ManualReset)) 
{ 
    int outdex = mediaServerMinConnections - 1; 
    for (int i = 0; i < mediaServerMinConnections; i++) 
    { 
    new Thread(() => 
    { 
     sshPool.Enqueue(new SshHandler()); 
     if (Interlocked.Decrement(ref outdex) < 1) 
     wh.Set(); 
    }).Start(); 
    } 
    wh.WaitOne(); 
} 

(Извинения для плотного стиля кодирования.)

Смежные вопросы