2013-08-02 4 views
7

두 클래스를 고려하십시오. ProducerConsumer (각각 고유 한 스레드가있는 클래식 패턴과 동일). ProducerEvent (Consumer)을 등록 할 수 있으며 생산자가 이벤트를 트리거하면 소비자의 이벤트 핸들러는 자체 스레드에서 실행됩니까? Producer의 이벤트가 자신의 스레드 또는 다른 내 를 트리거되는 경우 C# 스레드에 이벤트가 발생하면 추가됩니다.

  • Consumer

    모르는 : 여기 내 가정입니다.

  • 어느 ProducerConsumerControl의 후손이 그래서 그들은 BeginInvoke 방법은 상속하지 않아도됩니다.

ps. 나는 Producer - Consumer 패턴을 구현하려하지 않습니다. 이 두 클래스는 제작자를 리팩터링하여 스레드를 통합하려고합니다.

[업데이트]

더 내 문제를 확장하기 위해, 나는 가능한 한 가장 간단한 방법으로 함께 일 할 수있는 하드웨어 드라이버를 포장하기 위해 노력하고있어. 예를 들어, 내 래퍼는 주 응용 프로그램이 등록 할 StateChanged 이벤트를 가지므로 하드웨어 연결이 끊어졌을 때 알림을받습니다. 실제 드라이버는 그 존재를 확인하기위한 폴링 이외의 다른 수단이 없기 때문에 주기적으로 확인하기 위해 스레드를 시작해야합니다. 더 이상 사용할 수 없게되면 추가 된 동일한 스레드에서 실행해야하는 이벤트를 트리거합니다. 나는 이것이 고전적인 Producer-Consumer 패턴이라는 것을 알고 있지만, 드라이버 래퍼 사용을 단순화하려고하기 때문에 사용자 코드가 소비자를 구현하는 것을 원하지 않는다.

때문에이 문제에 대한 해결책이 없다 시킨다는 의견에 [업데이트], 나는 그들의 마음을 변경할 수있는 몇 줄을 추가하고 싶습니다. BeginInvoke이 내가 원하는 것을 할 수 있다고 생각하면, (적어도 이론에서는) 불가능해서는 안됩니다. 내 자신을 BeginInvoke 구현하고 Producer 내에서 그것을 호출하는 방법 중 하나입니다. 그것은 단지 내가 어떻게 BeginInvoke 그것을하는지 모르겠다!

+0

당신은 할 수 있지만 [실제 문제를 해결하기의 더 나은 방법이있을 수있다 (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). 이 이벤트 시스템을 사용하여 해결하려는 것을 설명 할 수 있습니까? –

+0

추가 확장이 필요한 경우 알려 주셔서 감사합니다. – Mehran

+0

훨씬 더 나은, 나는 그것을 해결하는 방법을 잘 모르겠지만 지금 당신의 문제가 무엇인지 이해합니다. –

답변

3

스레드 간 통신을 원합니다. 가능합니다. 사용 System.Windows.Threading.Dispatcher http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx

디스패처는 특정 스레드에 대한 작업 항목의 우선 순위 큐를 유지한다. Dispatcher가 스레드에서 작성되면 Dispatcher가 종료 된 경우에도 스레드와 연관 될 수있는 유일한 Dispatcher가됩니다. 현재 스레드에 대한 CurrentDispatcher를 가져 오려고 시도하고 Dispatcher가 스레드와 연관되어 있지 않으면 Dispatcher가 작성됩니다. Dispatcher는 DispatcherObject를 작성할 때도 작성됩니다. 백그라운드 스레드에서 Dispatcher를 작성하는 경우, 스레드를 종료하기 전에 디스패처를 종료해야합니다.

+0

감사합니다. 닷넷 4.5에서만 사용 가능하지만, 예제 코드를 포함 할 수 있다면 좋을 것입니다. – Mehran

+0

나쁘기 때문에 .Net 3 – Mehran

+0

물론 스레드 간 통신이 가능합니다. 가능한 많은 메커니즘이 있습니다. 이것이 @Andreas의 대답이 원래 질문에 대한 유일한 답이되는 이유입니다. –

3

예 이렇게하는 방법이 있습니다. 그것은 SynchronizationContext 클래스 (docs)를 사용합니다.동기화 컨텍스트는 Send (호출 스레드의 경우 동기식) 및 Post (호출 스레드의 경우 async) 메소드를 통해 한 스레드에서 다른 스레드로 메시지를 보내는 작업을 추상화합니다.

"생성자"스레드의 컨텍스트 인 캡처 동기화 컨텍스트 하나만 필요로하는 약간 간단한 상황을 생각해 보겠습니다. 다음과 같이하면됩니다 :

using System.Threading; 

class HardwareEvents 
{ 
    private SynchronizationContext context; 
    private Timer timer; 

    public HardwareEvents() 
    { 
     context = SynchronizationContext.Current ?? new SynchronizationContext(); 
     timer = new Timer(TimerMethod, null, 0, 1000); // start immediately, 1 sec interval. 
    } 

    private void TimerMethod(object state) 
    { 
     bool hardwareStateChanged = GetHardwareState(); 
     if (hardwareStateChanged) 
      context.Post(s => StateChanged(this, EventArgs.Empty), null); 
    } 

    public event EventHandler StateChanged; 

    private bool GetHardwareState() 
    { 
     // do something to get the state here. 
     return true; 
    } 
} 

이제 이벤트 생성시 스레드 생성 동기화 컨텍스트가 사용됩니다. 생성 스레드가 UI 스레드 인 경우 프레임 워크가 제공하는 동기화 컨텍스트를 갖습니다. 동기 컨텍스트가없는 경우, 스레드 풀에서 호출하는 기본 구현이 사용됩니다. SynchronizationContext은 생성자에서 소비자 스레드로 메시지를 보내는 사용자 지정 방법을 제공하려는 경우 하위 클래스로 지정할 수있는 클래스입니다. 해당 메시지를 보내려면 PostSend을 무시하십시오.

모든 이벤트 구독자가 자신의 스레드에서 다시 호출되기를 원하면 add 메서드에서 동기화 컨텍스트를 캡처해야합니다. 그런 다음 동기화 컨텍스트와 대리자 쌍을 유지합니다. 그런 다음 이벤트를 발생 시키면 동기화 컨텍스트/위임 쌍과 Post을 순서대로 반복합니다.

개선 할 수있는 몇 가지 다른 방법이 있습니다. 예를 들어, 이벤트에 등록자가없는 경우 하드웨어 폴링을 일시 중단 할 수 있습니다. 또는 하드웨어가 응답하지 않으면 폴링 빈도를 되돌릴 수 있습니다.

+0

'Timer'의 이벤트가 같은 스레드에서 잡히지 않았습니까? 이 멀티 스레드인가요? – Mehran

+0

@Mehraner 타이머의 이벤트는 같은 스레드에서 실행되지만 'context.Post' 함수는 호출 할 올바른 스레드로 정보를 보냅니다. 그것을 'Control.BeginInvoke'의 행동과 같이 생각하십시오. –

+0

@Mehran 그것은'System.Threading.Timer'입니다. 콜백은 스레드 풀 스레드에서 시작됩니다. 또 다른 스레드를 만들고 그 스레드를 주기적으로 체크인 할 수도 있습니다. 예를 들어, 하드웨어 API가 STA 아파트 모델을 필요로하는 경우에 필요합니다. 스레드 풀 스레드는 MTA입니다. –

1

이전에 게시 한 적이 있으며 좋은 해결책이 없다고 게시했습니다.

그러나 래퍼 객체를 만들 때 타이머 (즉, Windows.Forms.Timer)를 인스턴스화 할 수 있습니다. 이 타이머는 모든 Tick 이벤트를 UI 스레드에 게시합니다.

이제 장치 폴링 논리가 차단 및 빠르면 타이머 Tick 이벤트에 직접 구현하여 사용자 지정 이벤트를 발생시킬 수 있습니다.

그렇지 않으면 폴링 로직을 스레드 내에서 계속 수행 할 수 있으며 스레드 내부에서 이벤트를 실행하는 대신 10 밀리 초마다 타이머에서 읽은 일부 부울 변수를 뒤집은 다음 이벤트를 발생시킵니다.

이 솔루션은 여전히 ​​개체가 GUI 스레드에서 만들어 지도록 요구하지만 개체의 사용자는 적어도 Invoke에 대해 걱정할 필요가 없습니다.

+0

아무 것도 없으므로 원래 질문에 대한 대답이 아닙니다. –

0

가능합니다. 일반적인 접근법 중 하나는 BlockingCollection 클래스를 사용하는 것입니다. 이 데이터 구조는 큐가 비어있는 경우 dequeue 작업이 호출 스레드를 차단한다는 점을 제외하고는 일반적인 큐와 동일하게 작동합니다. 생성물은 Add을 호출하여 항목을 대기열에 넣고 소비자는 Take을 호출하여 항목을 대기열에 포함시킵니다. 일반적으로 소비자는 대기열에 항목이 나타날 때까지 기다리는 무한 루프를 돌리는 자체 전용 스레드를 실행합니다. 이것은 UI 스레드의 메시지 루프가 작동하는 방식이며, 마샬링 동작을 수행하기 위해 InvokeBeginInvoke 연산을 얻는 기초입니다.

public class Consumer 
{ 
    private BlockingCollection<Action> queue = new BlockingCollection<Action>(); 

    public Consumer() 
    { 
    var thread = new Thread(
    () => 
     { 
     while (true) 
     { 
      Action method = queue.Take(); 
      method(); 
     } 
     }); 
    thread.Start(); 
    } 

    public void BeginInvoke(Action method) 
    { 
    queue.Add(item); 
    } 
} 
+0

뚜렷한 질문에 대한 답변이 없으므로 아무 것도 없습니다. –

+0

@MartinJames : 특히 OP가 "사용자 코드가 소비자를 구현하는 것을 원하지 않습니다."라고 말하면서 동의 할 필요가 있습니다. 당신은 절대적으로 옳습니다 ... 당신은 임의의 스레드에 메소드의 실행을 주입 할 수 없습니다. –

3

첫째, .NET에서/기본 클래스 라이브러리는, 그것의 콜백 코드가 올바른 스레드에서 실행되도록 일반적으로 이벤트 가입자의 의무이므로주의하시기 바랍니다. 이벤트 제작자가 쉽게 만들 수 있습니다. 다양한 구독자의 스레드 유사성에 신경 쓰지 않고 이벤트를 트리거 할 수 있습니다.

가능한 구현의 단계별 설명은 다음과 같습니다.

간단한 설명부터 시작하겠습니다. Producer 클래스 및 해당 이벤트는 Event입니다. 내 예는 어떻게이 이벤트가 트리거됩니다 때 포함되지 않습니다 :

class Producer 
{ 
    public event EventHandler Event; // raised e.g. with `Event(this, EventArgs.Empty);` 
} 

다음으로, 우리는이 이벤트에 우리 Consumer 인스턴스를 구독하고 다시 특정 스레드에서 호출 할 수 있어야합니다 (I이 전화 할게 스레드 종류 "작업자 스레드") :

class Consumer 
{ 
    public void SubscribeToEventOf(Producer producer, WorkerThread targetWorkerThread) {…} 
} 

어떻게 구현하나요?

먼저 특정 작업자 스레드에 코드를 "전송"해야합니다. 원할 때마다 스레드가 특정 메소드를 실행하도록 할 수있는 방법이 없으므로 명시 적으로 작업 항목을 대기하도록 작업자 스레드를 정렬해야합니다. 이를 수행하는 한 가지 방법은 작업 항목 큐를 사용하는 것입니다.

sealed class WorkerThread 
{ 
    public WorkerThread() 
    { 
     this.workItems = new Queue<Action>(); 
     this.workItemAvailable = new AutoResetEvent(initialState: false); 
     new Thread(ProcessWorkItems) { IsBackground = true }.Start(); 
    } 

    readonly Queue<Action> workItems; 
    readonly AutoResetEvent workItemAvailable; 

    public void QueueWorkItem(Action workItem) 
    { 
     lock (workItems) // this is not extensively tested btw. 
     { 
      workItems.Enqueue(workItem); 
     } 
     workItemAvailable.Set(); 
    } 

    void ProcessWorkItems() 
    { 
     for (;;) 
     { 
      workItemAvailable.WaitOne(); 
      Action workItem; 
      lock (workItems) // dito, not extensively tested. 
      { 
       workItem = workItems.Dequeue(); 
       if (workItems.Count > 0) workItemAvailable.Set(); 
      } 
      workItem.Invoke(); 
     } 
    } 
} 

이 클래스는 기본적으로 스레드를 시작하고, 항목이 큐 (workItems)에 도착할 때까지 잠 (WaitOne) 폭포 무한 루프에 넣습니다 : 여기 WorkerThread에 대한 가능한 구현이다. 그런 일이 발생하면 — Action — 항목이 대기열에서 제외되고 호출됩니다. 그런 다음 스레드는 대기열에서 다른 항목을 사용할 수있을 때까지 다시 잠자기 상태가됩니다 (WaitOne).

ActionQueueWorkItem 메서드를 통해 대기열에 저장됩니다. 그래서 본질적으로 우리는 이제 해당 메소드를 호출하여 특정 WorkerThread 인스턴스로 실행될 코드를 보낼 수 있습니다. 이제 구현할 준비가되었습니다. Customer.SubscribeToEventOf :

class Consumer 
{ 
    public void SubscribeToEventOf(Producer producer, WorkerThread targetWorkerThread) 
    { 
     producer.Event += delegate(object sender, EventArgs e) 
     { 
      targetWorkerThread.QueueWorkItem(() => OnEvent(sender, e)); 
     }; 
    } 

    protected virtual void OnEvent(object sender, EventArgs e) 
    { 
     // this code is executed on the worker thread(s) passed to `Subscribe…`. 
    } 
} 

Voilà!


P.S. (자세히 설명하지 않음) :

sealed class WorkerThreadSynchronizationContext : SynchronizationContext 
{ 
    public WorkerThreadSynchronizationContext(WorkerThread workerThread) 
    { 
     this.workerThread = workerThread; 
    } 

    private readonly WorkerThread workerThread; 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     workerThread.QueueWorkItem(() => d(state)); 
    } 

    // other overrides for `Send` etc. omitted 
} 

그리고 WorkerThread.ProcessWorkItems의 시작, 당신이 '추가 기능, 당신은 표준 .NET 메커니즘이 SynchronizationContext라고 사용 WorkerThread에 코드를 전송하는 방법을 포장 수로 다음과 같이 특정 스레드에 대한 동기화 컨텍스트를 설정 D :

SynchronizationContext.SetSynchronizationContext(
    new WorkerThreadSynchronizationContext(this)); 
관련 문제