Monitor 클래스

정의

개체에 대한 액세스를 동기화하는 메커니즘을 제공합니다.

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
상속
Monitor
특성

예제

다음 예제에서는 클래스를 Monitor 사용하여 클래스가 나타내는 난수 생성기의 단일 인스턴스에 Random 대한 액세스를 동기화합니다. 이 예제에서는 각각 스레드 풀 스레드에서 비동기적으로 실행되는 10개의 태스크를 만듭니다. 각 태스크는 10,000개의 난수를 생성하고, 평균을 계산하고, 생성된 난수 수와 해당 합계의 실행 합계를 유지하는 두 개의 프로시저 수준 변수를 업데이트합니다. 모든 작업이 실행된 후 이 두 값을 사용하여 전체 평균을 계산합니다.

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

public class Example
{
   public static void Main()
   {
      List<Task> tasks = new List<Task>();
      Random rnd = new Random();
      long total = 0;
      int n = 0;
      
      for (int taskCtr = 0; taskCtr < 10; taskCtr++)
         tasks.Add(Task.Run( () => {  int[] values = new int[10000];
                                      int taskTotal = 0;
                                      int taskN = 0;
                                      int ctr = 0;
                                      Monitor.Enter(rnd);
                                         // Generate 10,000 random integers
                                         for (ctr = 0; ctr < 10000; ctr++)
                                            values[ctr] = rnd.Next(0, 1001);
                                      Monitor.Exit(rnd);
                                      taskN = ctr;
                                      foreach (var value in values)
                                         taskTotal += value;

                                      Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                        Task.CurrentId, (taskTotal * 1.0)/taskN,
                                                        taskN);
                                      Interlocked.Add(ref n, taskN);
                                      Interlocked.Add(ref total, taskTotal);
                                    } ));
      try {
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0)/n, n);
      }
      catch (AggregateException e) {
         foreach (var ie in e.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
      }
   }
}
// The example displays output like the following:
//       Mean for task  1: 499.04 (N=10,000)
//       Mean for task  2: 500.42 (N=10,000)
//       Mean for task  3: 499.65 (N=10,000)
//       Mean for task  8: 502.59 (N=10,000)
//       Mean for task  5: 502.75 (N=10,000)
//       Mean for task  4: 494.88 (N=10,000)
//       Mean for task  7: 499.22 (N=10,000)
//       Mean for task 10: 496.45 (N=10,000)
//       Mean for task  6: 499.75 (N=10,000)
//       Mean for task  9: 502.79 (N=10,000)
//
//       Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim tasks As New List(Of Task)()
      Dim rnd As New Random()
      Dim total As Long = 0
      Dim n As Integer = 0

      For taskCtr As Integer = 0 To 9
         tasks.Add(Task.Run( Sub()
                                Dim values(9999) As Integer
                                Dim taskTotal As Integer = 0
                                Dim taskN As Integer = 0
                                Dim ctr As Integer = 0
                                Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                    For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                    Next
                                Monitor.Exit(rnd)
                                taskN = ctr
                                For Each value in values
                                   taskTotal += value
                                Next
                                    
                                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal/taskN,
                                                  taskN)
                                Interlocked.Add(n, taskN)
                                Interlocked.Add(total, taskTotal)
                             End Sub ))
      Next
      
      Try
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine()
         Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0)/n, n)
      Catch e As AggregateException
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
         Next
      End Try
   End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

스레드 풀 스레드에서 실행되는 모든 작업에서 액세스할 수 있으므로 변수 total 에 액세스하고 n 동기화해야 합니다. 이 Interlocked.Add 메서드는 이 용도로 사용됩니다.

다음 예제에서는 클래스의 Monitor 결합된 사용(또는 SyncLock 언어 구문으로 lock 구현), 클래스 및 클래스를 Interlocked 보여 줍니다AutoResetEvent. 두 클래스 internal (C#) 또는 Friend (Visual Basic) 클래스를 정의하며UnSyncResource, SyncResource 각각 리소스에 대한 동기화 및 동기화되지 않은 액세스를 제공합니다. 이 예제에서 동기화된 액세스와 동기화되지 않은 액세스 간의 차이를 보여 주도록 하기 위해(각 메서드 호출이 빠르게 완료될 경우) 메서드는 임의의 지연을 포함합니다. 속성이 짝수인 Thread.ManagedThreadId 스레드의 경우 메서드는 2,000밀리초의 지연을 도입하기 위해 호출 Thread.Sleep 합니다. 클래스가 SyncResource 공용이 아니므로 클라이언트 코드 중 어느 것도 동기화된 리소스에 대한 잠금을 사용하지 않습니다. 내부 클래스 자체는 잠금을 사용합니다. 이렇게 하면 악성 코드가 공용 개체를 잠그지 못하게 됩니다.

using System;
using System.Threading;

internal class SyncResource
{
    // Use a monitor to enforce synchronization.
    public void Access()
    {
        lock(this) {
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId);
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

            Thread.Sleep(200);
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId);
        }
    }
}

internal class UnSyncResource
{
    // Do not enforce synchronization.
    public void Access()
    {
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId);
        if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
            Thread.Sleep(2000);

        Thread.Sleep(200);
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId);
    }
}

public class App
{
    private static int numOps;
    private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
    private static SyncResource SyncRes = new SyncResource();
    private static UnSyncResource UnSyncRes = new UnSyncResource();

   public static void Main()
   {
        // Set the number of synchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll synchronized operations have completed.\n");

        // Reset the count for unsynchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
   }

    static void SyncUpdateResource(Object state)
    {
        // Call the internal synchronized method.
        SyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }

    static void UnSyncUpdateResource(Object state)
    {
        // Call the unsynchronized method.
        UnSyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }
}
// The example displays output like the following:
//    Starting synchronized resource access on thread #6
//    Stopping synchronized resource access on thread #6
//    Starting synchronized resource access on thread #7
//    Stopping synchronized resource access on thread #7
//    Starting synchronized resource access on thread #3
//    Stopping synchronized resource access on thread #3
//    Starting synchronized resource access on thread #4
//    Stopping synchronized resource access on thread #4
//    Starting synchronized resource access on thread #5
//    Stopping synchronized resource access on thread #5
//
//    All synchronized operations have completed.
//
//    Starting unsynchronized resource access on Thread #7
//    Starting unsynchronized resource access on Thread #9
//    Starting unsynchronized resource access on Thread #10
//    Starting unsynchronized resource access on Thread #6
//    Starting unsynchronized resource access on Thread #3
//    Stopping unsynchronized resource access on thread #7
//    Stopping unsynchronized resource access on thread #9
//    Stopping unsynchronized resource access on thread #3
//    Stopping unsynchronized resource access on thread #10
//    Stopping unsynchronized resource access on thread #6
//
//    All unsynchronized thread operations have completed.
Imports System.Threading

Friend Class SyncResource
    ' Use a monitor to enforce synchronization.
    Public Sub Access()
        SyncLock Me
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
            If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
                Thread.Sleep(2000)
            End If
            Thread.Sleep(200)
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
        End SyncLock
    End Sub
End Class

Friend Class UnSyncResource
    ' Do not enforce synchronization.
    Public Sub Access()
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
        If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
            Thread.Sleep(2000)
        End If
        Thread.Sleep(200)
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
    End Sub
End Class

Public Module App
    Private numOps As Integer
    Private opsAreDone As New AutoResetEvent(False)
    Private SyncRes As New SyncResource()
    Private UnSyncRes As New UnSyncResource()

    Public Sub Main()
        ' Set the number of synchronized calls.
        numOps = 5
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
        Next
        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
        Console.WriteLine()

        numOps = 5
        ' Reset the count for unsynchronized calls.
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
        Next

        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
    End Sub

    Sub SyncUpdateResource()
        ' Call the internal synchronized method.
        SyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub

    Sub UnSyncUpdateResource()
        ' Call the unsynchronized method.
        UnSyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub
End Module
' The example displays output like the following:
'    Starting synchronized resource access on thread #6
'    Stopping synchronized resource access on thread #6
'    Starting synchronized resource access on thread #7
'    Stopping synchronized resource access on thread #7
'    Starting synchronized resource access on thread #3
'    Stopping synchronized resource access on thread #3
'    Starting synchronized resource access on thread #4
'    Stopping synchronized resource access on thread #4
'    Starting synchronized resource access on thread #5
'    Stopping synchronized resource access on thread #5
'
'    All synchronized operations have completed.
'
'    Starting unsynchronized resource access on Thread #7
'    Starting unsynchronized resource access on Thread #9
'    Starting unsynchronized resource access on Thread #10
'    Starting unsynchronized resource access on Thread #6
'    Starting unsynchronized resource access on Thread #3
'    Stopping unsynchronized resource access on thread #7
'    Stopping unsynchronized resource access on thread #9
'    Stopping unsynchronized resource access on thread #3
'    Stopping unsynchronized resource access on thread #10
'    Stopping unsynchronized resource access on thread #6
'
'    All unsynchronized thread operations have completed.

이 예제에서는 리소스에 액세스하려고 시도하는 스레드 수를 정의하는 변수 numOps를 정의합니다. 애플리케이션 스레드는 동기화된 액세스와 동기화되지 않은 액세스에 대해 각각 5번씩 ThreadPool.QueueUserWorkItem(WaitCallback) 메서드를 호출합니다. 메서드에는 ThreadPool.QueueUserWorkItem(WaitCallback) 매개 변수를 허용하지 않고 값을 반환하지 않는 대리자인 단일 매개 변수가 있습니다. 동기화된 액세스의 경우 메서드를 SyncUpdateResource 호출합니다. 동기화되지 않은 액세스의 경우 메서드를 UnSyncUpdateResource 호출합니다. 호출 애플리케이션 스레드가 각 일련의 메서드 호출 후는 AutoResetEvent.WaitOne 될 때까지 차단 한다는 메서드는 AutoResetEvent 인스턴스가 신호입니다.

메서드에 대한 각 호출은 SyncUpdateResource 내부 SyncResource.Access 메서드를 호출한 다음 메서드를 Interlocked.Decrement 호출하여 카운터를 numOps 감소합니다. 메서드는 Interlocked.Decrement 카운터를 감소시키는 데 사용됩니다. 그렇지 않으면 첫 번째 스레드의 감소된 값이 변수에 저장되기 전에 두 번째 스레드가 값에 액세스하도록 확신할 수 없기 때문입니다. 마지막으로 동기화된 작업자 스레드가 카운터를 0으로 감소하면 동기화된 모든 스레드가 리소스에 대한 액세스를 완료했음을 나타내며, SyncUpdateResource 메서드는 메서드를 호출 EventWaitHandle.Set 하여 주 스레드가 실행을 계속하도록 신호를 줍니다.

메서드에 대한 각 호출은 UnSyncUpdateResource 내부 UnSyncResource.Access 메서드를 호출한 다음 메서드를 Interlocked.Decrement 호출하여 카운터를 numOps 감소합니다. 다시 한 번 메서드 Interlocked.Decrement 는 첫 번째 스레드의 감소된 값이 변수에 할당되기 전에 두 번째 스레드가 값에 액세스하지 않도록 카운터를 감소시키는 데 사용됩니다. 마지막으로 동기화되지 않은 작업자 스레드가 카운터를 0으로 감소하면 더 이상 동기화되지 않은 스레드가 리소스에 액세스할 필요가 없음을 나타내며, UnSyncUpdateResource 메서드는 메서드를 호출 EventWaitHandle.Set 하여 주 스레드가 실행을 계속하도록 신호를 줍니다.

예제의 출력과 같이 동기화된 액세스는 다른 스레드가 액세스하기 전에 호출 스레드가 보호된 리소스를 종료하도록 합니다. 각 스레드는 선행 작업에서 대기합니다. 반면에 잠금 UnSyncResource.Access 이 없으면 스레드가 도달하는 순서대로 메서드가 호출됩니다.

설명

클래스 Monitor 를 사용하면 , 및 Monitor.Exit 메서드를 호출Monitor.EnterMonitor.TryEnter하여 특정 개체에 대한 잠금을 가져오고 해제하여 코드 영역에 대한 액세스를 동기화할 수 있습니다. 개체 잠금은 일반적으로 중요한 섹션이라고 하는 코드 블록에 대한 액세스를 제한하는 기능을 제공합니다. 스레드는 개체에 대한 잠금을 소유하지만 다른 스레드는 해당 잠금을 획득할 수 없습니다. 사용할 수도 있습니다는 Monitor 다른 스레드가 애플리케이션의 섹션에 액세스할 수 있는지 확인 하는 클래스 잠금 소유자에 의해 실행 되 고 다른 스레드가 잠겨 있는 다른 개체를 사용 하는 코드를 실행 중인 경우가 아니면 코드입니다.

이 문서의 내용

Monitor 클래스: 개요
잠금 개체
중요 섹션
Pulse, PulseAll 및 Wait
모니터 및 대기 핸들

Monitor 클래스: 개요

Monitor에 포함된 기능은 다음과 같습니다.

  • 요청 시 개체와 연결됩니다.

  • 언바운드입니다. 즉, 모든 컨텍스트에서 직접 호출할 수 있습니다.

  • 클래스의 인스턴스를 Monitor 만들 수 없습니다. 클래스의 Monitor 메서드는 모두 정적입니다. 각 메서드는 중요한 섹션에 대한 액세스를 제어하는 동기화된 개체를 전달합니다.

참고

클래스를 Monitor 사용하여 값 형식이 아닌 문자열(즉, 참조 형식 이외의 String참조 형식)이 아닌 개체를 잠급니다. 자세한 내용은 이 문서의 뒷부분에 있는 메서드 및 잠금 개체 섹션의 Enter 오버로드를 참조하세요.

다음 표에서는 동기화된 개체에 액세스하는 스레드에서 수행할 수 있는 작업에 대해 설명합니다.

작업 Description
Enter, TryEnter 개체에 대한 잠금을 획득합니다. 이 작업은 중요한 섹션의 시작 부분도 표시합니다. 다른 잠긴 개체를 사용하여 중요한 섹션의 지침을 실행하지 않는 한 다른 스레드는 위험 섹션을 입력할 수 없습니다.
Wait 다른 스레드가 개체를 잠그고 액세스하도록 허용하기 위해 개체의 잠금을 해제합니다. 다른 스레드가 개체에 액세스하는 동안 호출 스레드가 대기합니다. 펄스 신호는 대기 중인 스레드에 개체의 상태 변경 내용을 알리는 데 사용됩니다.
Pulse (신호), PulseAll 하나 이상의 대기 스레드에 신호를 보냅니다. 이 신호는 대기 스레드에 잠긴 개체의 상태가 변경되었음을 알리고 잠금 소유자가 잠금을 해제할 준비가 되었음을 알릴 수 있습니다. 대기 중인 스레드는 개체의 준비 큐에 배치되므로 결국 개체에 대한 잠금을 받을 수 있습니다. 스레드에 잠금이 있으면 개체의 새 상태를 확인하여 필요한 상태에 도달했는지 확인할 수 있습니다.
Exit 개체의 잠금을 해제합니다. 또한 이 작업은 잠긴 개체로 보호되는 중요한 섹션의 끝을 표시합니다.

.NET Framework 4부터는 두 가지 오버로드 집합과 TryEnter 메서드가 Enter 있습니다. 오버로드 ref 집합에는 잠금을 획득할 때 예외가 throw되더라도 잠금을 획득하는 true 경우 원자성으로 설정된 (C#) 또는 ByRef (Visual Basic) Boolean 매개 변수가 있습니다. 잠금이 보호하는 리소스가 일관된 상태가 아닐 수 있는 경우에도 모든 경우에 잠금을 해제하는 것이 중요한 경우 이러한 오버로드를 사용합니다.

잠금 개체

Monitor 클래스는 중요한 섹션에 static 대한 액세스를 제어하는 개체에서 작동하는 (C#) 또는 Shared (Visual Basic) 메서드로 구성됩니다. 동기화된 각 개체에 대해 다음 정보가 유지됩니다.

  • 현재 잠금을 보유하고 있는 스레드에 대한 참조입니다.

  • 잠금을 가져올 준비가 된 스레드를 포함하는 준비 큐에 대한 참조입니다.

  • 잠긴 개체의 상태 변경 알림을 기다리는 스레드가 포함된 대기 중인 큐에 대한 참조입니다.

Monitor 는 값 형식이 아닌 개체(즉, 참조 형식)를 잠식합니다. 값 형식 Enter 을 전달할 수 있지만 Exit각 호출에 대해 별도로 상자가 지정됩니다. 각 호출은 별도의 개체 Enter 를 만들고 차단하지 않으며 보호 중인 코드는 실제로 동기화되지 않습니다. 또한 전달된 개체가 전달된 Exit Enter개체와 다르므로 Monitor "개체 동기화 메서드가 동기화되지 않은 코드 블록에서 호출되었습니다."라는 메시지와 함께 예외를 throw SynchronizationLockException 합니다.

다음 예제에서는 이 문제를 보여 줍니다. 각각 250밀리초 동안 절전 모드인 10개의 작업을 시작합니다. 그런 다음 각 태스크는 실제로 시작되고 실행된 태스크 수를 계산하기 위한 카운터 변수 nTasks를 업데이트합니다. nTasks 여러 태스크에서 동시에 업데이트할 수 있는 전역 변수이므로 모니터를 사용하여 여러 태스크의 동시 수정으로부터 보호합니다. 그러나 예제의 출력에서 알 수 있듯이 각 태스크는 예외를 SynchronizationLockException throw합니다.

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

public class Example
{
   public static void Main()
   {

      int nTasks = 0;
      List<Task> tasks = new List<Task>();

      try {
         for (int ctr = 0; ctr < 10; ctr++)
            tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
                                        Thread.Sleep(250);
                                        // Increment the number of tasks.
                                        Monitor.Enter(nTasks);
                                        try {
                                           nTasks += 1;
                                        }
                                        finally {
                                           Monitor.Exit(nTasks);
                                        }
                                      } ));
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("{0} tasks started and executed.", nTasks);
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine("{0}", ie.GetType().Name);
            if (! msg.Contains(ie.Message))
               msg += ie.Message + Environment.NewLine;
         }
         Console.WriteLine("\nException Message(s):");
         Console.WriteLine(msg);
      }
   }
}
// The example displays the following output:
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//
//    Exception Message(s):
//    Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim nTasks As Integer = 0
      Dim tasks As New List(Of Task)()

      Try
         For ctr As Integer = 0 To 9
            tasks.Add(Task.Run( Sub()
                                   ' Instead of doing some work, just sleep.
                                   Thread.Sleep(250)
                                   ' Increment the number of tasks.
                                   Monitor.Enter(nTasks)
                                   Try
                                      nTasks += 1
                                   Finally
                                      Monitor.Exit(nTasks)
                                   End Try
                                End Sub))
         Next
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine("{0} tasks started and executed.", nTasks)
      Catch e As AggregateException
         Dim msg AS String = String.Empty
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}", ie.GetType().Name)
            If Not msg.Contains(ie.Message) Then
               msg += ie.Message + Environment.NewLine
            End If
         Next
         Console.WriteLine(vbCrLf + "Exception Message(s):")
         Console.WriteLine(msg)
      End Try
   End Sub
End Module
' The example displays the following output:
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'
'    Exception Message(s):
'    Object synchronization method was called from an unsynchronized block of code.

각 태스크는 SynchronizationLockException 변수가 각 태스크에서 메서드를 nTasks 호출하기 전에 boxed되기 때문에 예외를 Monitor.Enter throw합니다. 즉, 각 메서드 호출은 다른 변수와 독립적인 별도의 변수를 전달합니다. nTasks 는 메서드 호출에서 다시 boxed됩니다 Monitor.Exit . 다시 한 번, 서로 독립적인 nTasks10개의 새 boxed 변수와 메서드 호출에서 생성된 10개의 boxed 변수를 Monitor.Enter 만듭니다. 코드가 이전에 잠기지 않은 새로 만든 변수에 대한 잠금을 해제하려고 하기 때문에 예외가 throw됩니다.

다음 예제와 같이 호출 Enter 하기 전에 값 형식 변수를 입력하고 Exit두 메서드 모두에 동일한 boxed 개체를 전달할 수 있지만 이 작업을 수행하는 데는 이점이 없습니다. 언박싱되지 않은 변수에 대한 변경 내용은 박스형 복사본에 반영되지 않으며 상자가 있는 복사본의 값을 변경할 수 없습니다.

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

public class Example
{
   public static void Main()
   {

      int nTasks = 0;
      object o = nTasks;
      List<Task> tasks = new List<Task>();

      try {
         for (int ctr = 0; ctr < 10; ctr++)
            tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
                                        Thread.Sleep(250);
                                        // Increment the number of tasks.
                                        Monitor.Enter(o);
                                        try {
                                           nTasks++;
                                        }
                                        finally {
                                           Monitor.Exit(o);
                                        }
                                      } ));
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("{0} tasks started and executed.", nTasks);
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine("{0}", ie.GetType().Name);
            if (! msg.Contains(ie.Message))
               msg += ie.Message + Environment.NewLine;
         }
         Console.WriteLine("\nException Message(s):");
         Console.WriteLine(msg);
      }
   }
}
// The example displays the following output:
//        10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim nTasks As Integer = 0
      Dim o As Object = nTasks
      Dim tasks As New List(Of Task)()

      Try
         For ctr As Integer = 0 To 9
            tasks.Add(Task.Run( Sub()
                                   ' Instead of doing some work, just sleep.
                                   Thread.Sleep(250)
                                   ' Increment the number of tasks.
                                   Monitor.Enter(o)
                                   Try
                                      nTasks += 1
                                   Finally
                                      Monitor.Exit(o)
                                   End Try
                                End Sub))
         Next
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine("{0} tasks started and executed.", nTasks)
      Catch e As AggregateException
         Dim msg AS String = String.Empty
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}", ie.GetType().Name)
            If Not msg.Contains(ie.Message) Then
               msg += ie.Message + Environment.NewLine
            End If
         Next
         Console.WriteLine(vbCrLf + "Exception Message(s):")
         Console.WriteLine(msg)
      End Try
   End Sub
End Module
' The example displays the following output:
'       10 tasks started and executed.

동기화할 개체를 선택할 때는 프라이빗 또는 내부 개체에만 잠가야 합니다. 외부 개체를 잠그면 관련 없는 코드가 다른 용도로 잠글 동일한 개체를 선택할 수 있으므로 교착 상태가 발생할 수 있습니다.

잠금에 사용 되는 개체에서 파생 되는 경우 여러 애플리케이션 도메인의 개체에 동기화 할 수 있는 참고 MarshalByRefObject합니다.

중요 섹션

Exit 메서드를 Enter 사용하여 중요한 섹션의 시작과 끝을 표시합니다.

참고

Exit 메서드에서 제공하는 Enter 기능은 언어 구문이 메서드 오버로드 및 메서드를 래핑 Monitor.Enter(Object, Boolean) 한다는 점을 제외하고 C#의 lock 문과 Visual Basic SyncLock 문에서 try제공하는 기능과 Monitor.Exit 동일합니다.finally 차단하여 모니터가 해제되었는지 확인합니다.

중요한 섹션이 연속 명령 집합인 경우 메서드에서 얻은 Enter 잠금은 단일 스레드만 잠긴 개체로 묶인 코드를 실행할 수 있음을 보장합니다. 이 경우 해당 코드를 블록에 try 배치하고 블록에 메서드 finally 에 대한 호출을 Exit 배치하는 것이 좋습니다. 이렇게 하면 예외가 발생하더라도 잠금이 해제됩니다. 다음 코드 조각에서는 이 패턴을 보여 줍니다.

// Define the lock object.
var obj = new Object();

// Define the critical section.
Monitor.Enter(obj);
try {
   // Code to execute one thread at a time.
}
// catch blocks go here.
finally {
   Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()

' Define the critical section.
Monitor.Enter(obj)
Try 
   ' Code to execute one thread at a time.

' catch blocks go here.
Finally 
   Monitor.Exit(obj)
End Try

이 기능은 일반적으로 클래스의 정적 또는 인스턴스 메서드에 대한 액세스를 동기화하는 데 사용됩니다.

중요한 섹션이 전체 메서드에 걸쳐 있는 경우 메서드에 배치 System.Runtime.CompilerServices.MethodImplAttribute 하고 생성자System.Runtime.CompilerServices.MethodImplAttribute에서 값을 지정하여 Synchronized 잠금 기능을 달성할 수 있습니다. 이 특성을 Enter 사용하는 경우 메서드 호출 및 Exit 메서드가 필요하지 않습니다. 다음 코드 조각에서는 이 패턴을 보여 줍니다.

[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
   // Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
   ' Method implementation.
End Sub

이 특성으로 인해 메서드가 반환될 때까지 현재 스레드가 잠금을 유지합니다. 잠금을 더 빨리 해제할 수 있으면 클래스, C# lock 문 또는 특성 대신 메서드 내부의 Visual Basic SyncLock 문을 사용합니다Monitor.

지정된 개체를 Enter 잠그고 해제하는 문과 Exit 멤버 또는 클래스 경계를 넘거나 둘 다 해제하는 문은 가능하지만 이 방법은 권장되지 않습니다.

Pulse, PulseAll 및 Wait

스레드가 잠금을 소유하고 잠금이 보호하는 중요한 섹션에 들어가면 , Monitor.PulseMonitor.PulseAll 메서드를 Monitor.Wait호출할 수 있습니다.

잠금을 포함하는 스레드가 호출 Wait되면 잠금이 해제되고 스레드가 동기화된 개체의 대기 큐에 추가됩니다. 준비 큐의 첫 번째 스레드(있는 경우)는 잠금을 획득하고 중요한 섹션에 들어갑니다. 호출 Wait 된 스레드는 잠금을 보유하는 스레드에서 메서드를 호출할 Monitor.PulseAllMonitor.Pulse 대기 중인 큐에서 준비 큐로 이동됩니다(이동하려면 스레드가 대기 중인 큐의 맨 앞에 있어야 함). Wait 호출 스레드가 잠금을 다시 가져올 때 메서드가 반환됩니다.

잠금 호출을 보유하는 스레드가 호출 Pulse되면 대기 중인 큐의 헤드에 있는 스레드가 준비 큐로 이동됩니다. 메서드를 호출하면 PulseAll 대기 중인 큐의 모든 스레드가 준비 큐로 이동합니다.

모니터 및 대기 핸들

클래스와 WaitHandle 개체의 사용의 차이점을 유의해야 Monitor 합니다.

  • 클래스는 Monitor 순수하게 관리되고 완전히 이식 가능하며 운영 체제 리소스 요구 사항 측면에서 더 효율적일 수 있습니다.

  • WaitHandle 개체는 운영 체제 대기 가능 개체를 나타내고, 관리 코드와 관리되지 않는 코드 간의 동기화에 유용하며, 여러 개체를 한 번에 대기하는 기능과 같은 일부 고급 운영 체제 기능을 노출합니다.

속성

LockContentionCount

모니터의 잠금을 시도할 때 경합이 발생한 횟수를 가져옵니다.

메서드

Enter(Object)

지정된 개체의 단독 잠금을 가져옵니다.

Enter(Object, Boolean)

지정된 개체의 단독 잠금을 가져오고 잠금 설정 여부를 나타내는 값을 자동으로 설정합니다.

Exit(Object)

지정된 개체의 단독 잠금을 해제합니다.

IsEntered(Object)

현재 스레드가 지정된 개체에 대한 잠금을 보유하는지 여부를 확인합니다.

Pulse(Object)

대기 중인 큐에 포함된 스레드에 잠겨 있는 개체의 상태 변경을 알립니다.

PulseAll(Object)

대기 중인 모든 스레드에 개체 상태 변경을 알립니다.

TryEnter(Object)

지정된 개체의 단독 잠금을 가져오려고 했습니다.

TryEnter(Object, Boolean)

지정된 개체의 단독 잠금을 가져오고 잠금 설정 여부를 나타내는 값을 자동으로 설정하려고 시도합니다.

TryEnter(Object, Int32)

지정된 시간(밀리초) 동안 지정된 개체의 단독 잠금을 가져오려고 했습니다.

TryEnter(Object, Int32, Boolean)

지정된 시간(밀리초) 동안 지정된 개체의 단독 잠금을 가져오고 잠금 설정 여부를 나타내는 값을 자동으로 설정하려고 시도합니다.

TryEnter(Object, TimeSpan)

지정된 시간 동안 지정된 개체의 단독 잠금을 가져오려고 했습니다.

TryEnter(Object, TimeSpan, Boolean)

지정된 시간 동안 지정된 개체의 단독 잠금을 가져오고 잠금 설정 여부를 나타내는 값을 자동으로 설정하려고 시도합니다.

Wait(Object)

개체의 잠금을 해제한 다음 잠금을 다시 가져올 때까지 현재 스레드를 차단합니다.

Wait(Object, Int32)

개체의 잠금을 해제한 다음 잠금을 다시 가져올 때까지 현재 스레드를 차단합니다. 지정된 시간 제한 간격이 지나면 스레드가 준비된 큐에 들어갑니다.

Wait(Object, Int32, Boolean)

개체의 잠금을 해제한 다음 잠금을 다시 가져올 때까지 현재 스레드를 차단합니다. 지정된 시간 제한 간격이 지나면 스레드가 준비된 큐에 들어갑니다. 또한 이 메서드는 컨텍스트의 동기화 도메인(동기화된 컨텍스트에 있는 경우)을 대기 전에 종료하고 나중에 다시 가져오는지 여부도 지정합니다.

Wait(Object, TimeSpan)

개체의 잠금을 해제한 다음 잠금을 다시 가져올 때까지 현재 스레드를 차단합니다. 지정된 시간 제한 간격이 지나면 스레드가 준비된 큐에 들어갑니다.

Wait(Object, TimeSpan, Boolean)

개체의 잠금을 해제한 다음 잠금을 다시 가져올 때까지 현재 스레드를 차단합니다. 지정된 시간 제한 간격이 지나면 스레드가 준비된 큐에 들어갑니다. 필요에 따라 동기화된 컨텍스트의 동기화 도메인을 대기 전에 종료하고 나중에 해당 도메인을 다시 가져옵니다.

적용 대상

스레드 보안

이 형식은 스레드로부터 안전합니다.

추가 정보