스레드가 .NET으로 끝나기를 기다리는 방법은 무엇입니까?


178

C #에서는 메인 UI 스레드뿐만 아니라 두 개의 스레드가 필요한 스레딩을 실제로 사용한 적이 없습니다. 기본적으로 다음과 같은 것이 있습니다.

public void StartTheActions()
{
  //Starting thread 1....
  Thread t1 = new Thread(new ThreadStart(action1));
  t1.Start();

  // Now, I want for the main thread (which is calling `StartTheActions` method) 
  // to wait for `t1` to finish. I've created an event in `action1` for this. 
  // The I wish `t2` to start...

  Thread t2 = new Thread(new ThreadStart(action2));
  t2.Start();
}

따라서 본질적으로 내 질문은 스레드가 다른 스레드가 끝날 때까지 기다리는 방법입니다. 가장 좋은 방법은 무엇입니까?


4
"... 전에 스레딩을 사용한 적이 없다면 ..."* Async () 패턴과 같은 더 간단한 대안을 고려할 수 있습니다.
Ðаn

4
어쨌든 스레드 1이 끝나기를 기다리고 있다면 왜 그 메소드를 동기식으로 호출하지 않습니까?
Svish

13
선형 방식으로 처리 할 때 스레드를 사용하는 요점은 무엇입니까?
John

1
@ 존, 사용자가 작업하는 동안 작동하는 백그라운드 스레드를 회전시키는 데 많은 용도가 있다는 것이 전적으로 의미가 있습니다. 또한 귀하의 질문이 이전 질문과 동일하지 않습니까?
user34660

쉽게 사용하기 위해 backgroundworker를 사용하는 Rotem의 답변 은 매우 간단합니다.
Kamil

답변:


264

5 가지 옵션을 사용할 수 있습니다.

1. 스레드. 조인

미치의 대답과 마찬가지로. 그러나 이것은 UI 스레드를 차단하지만 타임 아웃이 내장되어 있습니다.


2. 사용 WaitHandle

ManualResetEvent 이다 WaitHandle 제안 jrista으로는.

WaitHandle.WaitAll()MTA 스레드가 필요하기 때문에 여러 스레드를 대기하려는 경우 기본적으로 작동하지 않습니다. Main()방법을 표시 하여이 문제를 해결할 수 MTAThread있지만 메시지 펌프를 차단하므로 읽은 내용에서 권장하지 않습니다.


3. 이벤트를 시작

참조 존 소총으로이 페이지 이벤트 및 멀티 스레딩에 대한을,이 이벤트가 사이 unsubcribed이 될 수 있다는 가능성 ifEventName(this,EventArgs.Empty)- 그 전에 나에게 일어난.

(다행 스럽게도이 컴파일은 시도하지 않았습니다)

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();
        worker.ThreadDone += HandleThreadDone;

        Thread thread1 = new Thread(worker.Run);
        thread1.Start();

        _count = 1;
    }

    void HandleThreadDone(object sender, EventArgs e)
    {
        // You should get the idea this is just an example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();
            worker.ThreadDone += HandleThreadDone;

            Thread thread2 = new Thread(worker.Run);
            thread2.Start();

            _count++;
        }
    }

    class ThreadWorker
    {
        public event EventHandler ThreadDone;

        public void Run()
        {
            // Do a task

            if (ThreadDone != null)
                ThreadDone(this, EventArgs.Empty);
        }
    }
}

4. 대리인 사용

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();

        Thread thread1 = new Thread(worker.Run);
        thread1.Start(HandleThreadDone);

        _count = 1;
    }

    void HandleThreadDone()
    {
        // As before - just a simple example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();

            Thread thread2 = new Thread(worker.Run);
            thread2.Start(HandleThreadDone);

            _count++;
        }
    }

    class ThreadWorker
    {
        // Switch to your favourite Action<T> or Func<T>
        public void Run(object state)
        {
            // Do a task

            Action completeAction = (Action)state;
            completeAction.Invoke();
        }
    }
}

_count 메소드를 사용하는 경우 다음을 사용하여 늘리는 것이 안전 할 수 있습니다.

Interlocked.Increment(ref _count)

스레드 알림을 위해 델리게이트와 이벤트를 사용하는 것의 차이점을 알고 싶습니다. 알고있는 유일한 차이점은 이벤트가 동 기적으로 호출된다는 것입니다.


5. 대신 비동기 적으로 수행

에 대한 대답 이 질문은 이 방법으로 옵션의 아주 명확한 설명이 있습니다.


잘못된 스레드의 델리게이트 / 이벤트

이벤트 / 델리게이트 방식은 이벤트 핸들러 메소드기본 UI 스레드가 아닌 thread1 / thread2에 있음 을 의미 하므로 HandleThreadDone 메소드의 맨 위에서 오른쪽으로 다시 전환해야합니다.

// Delegate example
if (InvokeRequired)
{
    Invoke(new Action(HandleThreadDone));
    return;
}

61

더하다

t1.Join();    // Wait until thread t1 finishes

시작한 후에는 메인 스레드에서 실행하는 것과 본질적으로 동일한 결과이므로 많은 것을 달성하지 못합니다!

.NET의 스레딩에 대한 이해를 얻으려면 C # 무료 전자 책 에서 Joe Albahari의 스레딩을 읽는 것이 좋습니다 .


4
비록 Join이 일반적으로 매우 나쁠 수의 아스 커 묻는 된 것으로 보이는 그대로입니다. 에 대한 호출 Join은이 작업을 수행하는 스레드를 끊습니다. 이것이 메인 GUI 스레드라면, 이것은 BAD입니다 ! 사용자로서 나는 이런 식으로 작동하는 것처럼 보이는 응용 프로그램을 적극적으로 몹시 싫어합니다. 이 질문에 대한 다른 모든 답변과 stackoverflow.com/questions/1221374/…
peSHIr

1
나는 일반적으로 Join ()이 나쁘다는 것에 동의합니다. 나는 아마도 내 대답에서 충분히 명확하게하지 못했습니다.
Mitch Wheat

2
여러분, 하나의 크기가 모든 것에 맞지는 않습니다 . 스레드가 실제로 작업을 완료해야하는 상황이 있습니다. 스레드가 데이터를 처리하고 변경하려고합니다. 이 경우 스레드를 정상적으로 취소하도록 알리고 완료 될 때까지 (특히 한 단계가 매우 빠르게 처리 될 때까지) 기다리면 IMO가 완전히 정당화됩니다. 차라리 Join은 악하다 (C ++ FAQ에서). 실제로 필요한 경우가 아니면 사용하지 않습니다.
Spook

5
Join은 도구라는 점을 분명히 말하고 싶습니다. 사실에도 불구하고 매우 자주 잘못 사용된다는 사실에도 불구하고 유용합니다. 불필요한 부작용없이 작동하는 상황이 있습니다 (예 : 메인 GUI 스레드를 눈에 띄게 정지시키는 것과 같이).
Spook

2
가입은 도구입니까? 나는 그것이 방법이라고 생각할 것입니다.
Mitch Wheat

32

앞의 두 가지 대답은 훌륭하며 간단한 시나리오에서 작동합니다. 그러나 스레드를 동기화하는 다른 방법이 있습니다. 다음도 작동합니다.

public void StartTheActions()
{
    ManualResetEvent syncEvent = new ManualResetEvent(false);

    Thread t1 = new Thread(
        () =>
        {
            // Do some work...
            syncEvent.Set();
        }
    );
    t1.Start();

    Thread t2 = new Thread(
        () =>
        {
            syncEvent.WaitOne();

            // Do some work...
        }
    );
    t2.Start();
}

ManualResetEvent 는 .NET 프레임 워크가 제공 해야하는 다양한 WaitHandle 중 하나입니다 . lock () / Monitor, Thread.Join 등과 같은 단순하지만 매우 일반적인 도구보다 훨씬 풍부한 스레드 동기화 기능을 제공 할 수 있습니다. 또한 '마스터'스레드와 같은 복잡한 시나리오를 허용하는 두 개 이상의 스레드를 동기화하는 데 사용할 수 있습니다. 여러 '하위'스레드, 서로 동기화 될 여러 단계에 종속 된 여러 동시 프로세스 등을 조정하는 등


30

.NET 4에서 사용하는 경우이 샘플이 도움이 될 수 있습니다.

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
    }
}

에서 : https://stackoverflow.com/a/4190969/1676736


8
귀하의 답변에 스레드에 대해서는 언급하지 않았습니다. 문제는 작업이 아닌 스레드에 관한 것입니다. 둘은 동일하지 않습니다.
Suamere

7
나는 원래 포스터와 비슷한 질문 (및 지식 수준)을 가지고 있었고이 답변은 나에게 매우 귀중했습니다. 작업은 내가하고있는 일에 더 적합 하며이 답변을 찾지 못하면 나는 내 글을 쓸 것입니다. 자신의 끔찍한 스레드 풀.
Chris Rae

1
@ChrisRae, 이것은 원래 질문에 대한 주석이어야하며 이와 같은 대답은 아닙니다.
Jaime Hablutzel

이 특정 답변에 관한 것이므로 아마도 여기에 더 합리적이라고 생각합니다.
Chris Rae

1
@Suamere가 말했듯 이이 답변은 전적으로 OP 질문과 관련이 없습니다.
Glenn Slayden 2018 년


4

메인 스레드가 첫 번째 스레드에 콜백 메소드를 전달하도록하고, 완료되면 메인 스레드에서 콜백 메소드를 호출하여 두 번째 스레드를 시작할 수 있습니다. 이렇게하면 Join 또는 Waithandle을 기다리는 동안 주 스레드가 중단되지 않습니다. 어쨌든 C #으로 배우는 데 유용한 메서드를 대리자로 전달하는 것이 좋습니다.


0

이 시도:

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

foreach (Thread curThread in myThreads)
{
    curThread.Start();
}

foreach (Thread curThread in myThreads)
{
    curThread.Join();
}

0

어쩌면 다른 사람들을 돕기 위해 게시하면 내가 생각해 낸 것과 같은 해결책을 찾는 데 꽤 많은 시간을 보냈습니다. 그래서 나는 조금 다른 접근법을 취했습니다. 위의 카운터 옵션이 있는데 조금 다르게 적용했습니다. 나는 많은 스레드를 돌리고 카운터를 늘리고 스레드가 시작되고 중지됨에 따라 카운터를 줄였습니다. 그런 다음 주요 방법으로 일시 중지하고 스레드가 완료 될 때까지 기다리려고했습니다.

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

내 블로그에 문서화되어 있습니다. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/


4
이것을 통화 중 대기라고합니다. 예, 작동하고 때로는 최상의 솔루션이지만 CPU 시간을 낭비하므로 가능하면 피하고 싶습니다.
MobileMon

규칙보다는 지침이되는 @MobileMon. 이 경우 루프가 CPU의 0.00000001 %를 낭비하고 OP가 C #으로 코딩하고 있기 때문에이를 '더 효율적인'것으로 바꾸는 것은 시간 낭비입니다. 최적화의 첫 번째 규칙은-아닙니다. 먼저 측정하십시오.
Spike0xff

0

작업이 완료되기를 기다리는 동안 UI가 디스플레이를 업데이트 할 수있게하려면 스레드에서 IsAlive를 테스트하는 while 루프를 사용합니다.

    Thread t = new Thread(() => someMethod(parameters));
    t.Start();
    while (t.IsAlive)
    {
        Thread.Sleep(500);
        Application.DoEvents();
    }

-1

다음은 동일한 클래스 내에서 트레드가 완료 될 때까지 기다리는 간단한 예입니다. 또한 동일한 네임 스페이스에서 다른 클래스를 호출합니다. button1을 생성하는 한 Winform으로 실행할 수 있도록 "using"문을 포함 시켰습니다.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace ClassCrossCall
{

 public partial class Form1 : Form
 {
  int number = 0; // this is an intentional problem, included for demonstration purposes
  public Form1()
  {
   InitializeComponent();
  }
  private void Form1_Load(object sender, EventArgs e)
  {
   button1.Text="Initialized";
  }
  private void button1_Click(object sender, EventArgs e)
  {
   button1.Text="Clicked";
   button1.Refresh();
   Thread.Sleep(400);
   List<Task> taskList = new List<Task>();
   taskList.Add(Task.Factory.StartNew(() => update_thread(2000)));
   taskList.Add(Task.Factory.StartNew(() => update_thread(4000)));
   Task.WaitAll(taskList.ToArray());
   worker.update_button(this,number);
  }
  public void update_thread(int ms)
  {
   // It's important to check the scope of all variables
   number=ms; // this could be either 2000 or 4000.  Race condition.
   Thread.Sleep(ms);
  }
 }

 class worker
 {
  public static void update_button(Form1 form, int number)
  {
   form.button1.Text=$"{number}";
  }
 }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.