杰瑞科技汇

Python win32event如何创建与使用事件?

Of course! The win32event module is a part of the pywin32 library, which provides Python interfaces to many of the Windows APIs. It's a powerful tool for inter-process communication (IPC) and managing synchronization within a single process or across multiple processes.

Python win32event如何创建与使用事件?-图1
(图片来源网络,侵删)

Let's break down what it is, why you'd use it, and how to use it with clear examples.


What is win32event?

The win32event module allows you to create and manipulate Windows synchronization objects. Think of these as traffic signals for your program's threads and processes. They ensure that operations happen in the correct order and prevent conflicts, like two threads trying to write to the same file at the same time.

The most common objects you'll work with are:

  • CreateEvent: A signal that can be either in a "signaled" (on) or "non-signaled" (off) state. It's a one-time notification. Once a thread waits for it and it's signaled, it automatically resets to non-signaled (unless you create it as a "manual reset" event).
  • CreateMutex: A mutual exclusion object. Only one thread or process can own the mutex at a time. If a thread tries to acquire a locked mutex, it will block (wait) until the owner releases it.
  • CreateSemaphore: A counter-based synchronization object. It allows a specified number of threads to access a resource simultaneously. When the count reaches zero, threads must wait.
  • CreateWaitableTimer: A timer that can be set to signal after a specific amount of time or at a specific time.

Installation

First, you need to install the pywin32 library. It's not part of the standard Python distribution.

Python win32event如何创建与使用事件?-图2
(图片来源网络,侵删)
pip install pywin32

Core Concepts: Waiting and Signaling

The two fundamental operations you'll perform are:

  1. Waiting: A thread pauses its execution until a synchronization object becomes "signaled".
    • win32event.WaitForSingleObject(h_object, timeout_ms): Waits for one specific object.
    • win32event.WaitForMultipleObjects(list_of_objects, wait_all, timeout_ms): Waits for one or more objects in a list to become signaled.
  2. Signaling: An action that sets the object's state to "signaled", which wakes up any waiting threads.
    • win32event.SetEvent(h_event): Signals an event.
    • win32event.ReleaseMutex(h_mutex): Releases a mutex.
    • win32event.ReleaseSemaphore(h_semaphore, release_count): Increments a semaphore's count.

Practical Examples

Let's look at examples for the most common objects.

Example 1: CreateEvent - Thread Synchronization

This is a classic pattern where a main thread starts one or more "worker" threads and then waits for them to finish their tasks.

import win32event
import win32api
import threading
import time
# A worker function that will signal when it's done
def worker(event_handle):
    print(f"Worker {threading.current_thread().name}: Starting work...")
    time.sleep(3)  # Simulate a long task
    print(f"Worker {threading.current_thread().name}: Work finished. Signaling event.")
    # Signal the event to let the main thread know we're done
    win32event.SetEvent(event_handle)
# --- Main Program ---
if __name__ == "__main__":
    print("Main: Creating an auto-reset event.")
    # Create an auto-reset event. It's initially non-signaled.
    # The bManualReset=False means it automatically resets to non-signaled
    # after a single waiting thread is released.
    event_handle = win32event.CreateEvent(None, 0, 0, None)
    print("Main: Starting worker thread...")
    worker_thread = threading.Thread(target=worker, args=(event_handle,))
    worker_thread.start()
    print("Main: Waiting for the worker thread to signal...")
    # Wait for the event to become signaled.
    # INFINITE means wait forever. You could also use a timeout in milliseconds (e.g., 5000).
    result = win32event.WaitForSingleObject(event_handle, win32event.INFINITE)
    if result == win32event.WAIT_OBJECT_0:
        print("Main: Event was signaled! Worker has finished.")
    else:
        print("Main: Wait failed or timed out.")
    # Clean up the handle (important!)
    win32api.CloseHandle(event_handle)
    worker_thread.join()
    print("Main: Program finished.")

Output:

Python win32event如何创建与使用事件?-图3
(图片来源网络,侵删)
Main: Creating an auto-reset event.
Main: Starting worker thread...
Main: Waiting for the worker thread to signal...
Worker Thread-1: Starting work...
Worker Thread-1: Work finished. Signaling event.
Main: Event was signaled! Worker has finished.
Main: Program finished.

Example 2: CreateMutex - Protecting a Shared Resource

Imagine two threads trying to print to the console at the same time, which would result in garbled output. A mutex can serialize their access.

import win32event
import win32api
import threading
import time
# A shared resource (in this case, the console)
SHARED_RESOURCE = "Hello from thread "
def worker(mutex_handle, thread_id):
    print(f"Worker {thread_id}: Attempting to acquire mutex...")
    # Wait for the mutex to be available. This will block if another thread holds it.
    win32event.WaitForSingleObject(mutex_handle, win32event.INFINITE)
    print(f"Worker {thread_id}: Mutex acquired. Accessing shared resource.")
    # Critical section: only one thread can be here at a time
    for _ in range(3):
        print(f"{SHARED_RESOURCE}{thread_id}")
        time.sleep(0.5)
    print(f"Worker {thread_id}: Releasing mutex.")
    # Release the mutex so other threads can use it
    win32event.ReleaseMutex(mutex_handle)
# --- Main Program ---
if __name__ == "__main__":
    print("Main: Creating mutex.")
    # Create a mutex. It's initially not owned by anyone.
    mutex_handle = win32event.CreateMutex(None, 0, "Global\\MyPythonMutex")
    threads = []
    for i in range(2):
        t = threading.Thread(target=worker, args=(mutex_handle, i))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("\nMain: All workers finished.")
    win32api.CloseHandle(mutex_handle)

Output:

Main: Creating mutex.
Worker 0: Attempting to acquire mutex...
Worker 0: Mutex acquired. Accessing shared resource.
Hello from thread 0
Hello from thread 0
Hello from thread 0
Worker 0: Releasing mutex.
Worker 1: Attempting to acquire mutex...
Worker 1: Mutex acquired. Accessing shared resource.
Hello from thread 1
Hello from thread 1
Hello from thread 1
Worker 1: Releasing mutex.
Main: All workers finished.

Notice how the output from thread 0 is completely printed before thread 1 gets a turn. Without the mutex, the output would be interleaved.

Example 3: Inter-Process Communication (IPC) with a Named Event

This is one of the most powerful uses of win32event. You can create a named event that both a parent process and a child process (or any two unrelated processes) can access.

Parent Process (parent.py):

import win32event
import win32api
import subprocess
import time
print("Parent: Creating a named event.")
# Create a named, manual-reset event. It's initially non-signaled.
# The name must be unique or known to both processes.
# 'Global\\' makes it system-wide. 'Local\\' is for the current session.
event_handle = win32event.CreateEvent(None, 1, 0, "Global\\MyIPCEvent")
print("Parent: Starting child process...")
# Start the child process. It must be in the same directory or have a full path.
proc = subprocess.Popen(['python', 'child.py'])
print("Parent: Waiting for child to be ready...")
# Wait for the child to signal the event
win32event.WaitForSingleObject(event_handle, win32event.INFINITE)
print("Parent: Child is ready! Proceeding with work...")
# Do some work
time.sleep(2)
print("Parent: Work finished. Signaling child to exit.")
# Signal the child one last time to shut down
win32event.SetEvent(event_handle)
proc.wait()
print("Parent: Child has exited. Cleaning up.")
win32api.CloseHandle(event_handle)

Child Process (child.py):

import win32event
import win32api
import time
print("Child: Attempting to open the named event...")
# Open an existing named event. Do not create it.
try:
    event_handle = win32event.OpenEvent(win32event.EVENT_ALL_ACCESS, False, "Global\\MyIPCEvent")
except Exception as e:
    print(f"Child: Error opening event: {e}")
    exit()
print("Child: Event opened. Waiting for parent to signal...")
# Wait for the initial signal from the parent
win32event.WaitForSingleObject(event_handle, win32event.INFINITE)
print("Child: Parent signaled. I am now ready.")
# Do some work
time.sleep(1)
print("Child: My work is done. Waiting for final signal from parent...")
# Wait for the final signal to shut down
win32event.WaitForSingleObject(event_handle, win32event.INFINITE)
print("Child: Final signal received. Exiting.")
win32api.CloseHandle(event_handle)

To Run:

  1. Save the two code blocks as parent.py and child.py in the same directory.
  2. Run python parent.py.

Expected Output:

Parent: Creating a named event.
Parent: Starting child process...
Child: Attempting to open the named event...
Child: Event opened. Waiting for parent to signal...
Parent: Waiting for child to be ready...
Parent: Child is ready! Proceeding with work...
Child: Parent signaled. I am now ready.
Child: My work is done. Waiting for final signal from parent...
Parent: Work finished. Signaling child to exit.
Child: Final signal received. Exiting.
Parent: Child has exited. Cleaning up.

Key win32event Functions and Constants

Function Description
CreateEvent(...) Creates an event object. bManualReset determines if it stays signaled. bInitialState sets the initial state.
OpenEvent(...) Opens an existing named event.
SetEvent(h_event) Sets an event to the signaled state. Wakes up all waiters (if manual reset) or one (if auto-reset).
ResetEvent(h_event) Sets an event to the non-signaled state.
CreateMutex(...) Creates a mutex object.
OpenMutex(...) Opens an existing named mutex.
ReleaseMutex(h_mutex) Releases a mutex, allowing another thread to acquire it.
CreateSemaphore(...) Creates a semaphore object.
OpenSemaphore(...) Opens an existing named semaphore.
ReleaseSemaphore(...) Releases a semaphore, incrementing its count.
WaitForSingleObject(h, timeout) Waits for one object to become signaled. timeout can be 0 (don't wait), INFINITE (wait forever), or a number of ms.
WaitForMultipleObjects(...) Waits for one or more objects in a list.
Constant Value Meaning
WAIT_OBJECT_0 0 The object was signaled.
WAIT_TIMEOUT 0x102 The wait timed out.
WAIT_ABANDONED 0x00000080 A wait for a mutex was satisfied due to another thread terminating.
WAIT_FAILED -1 The call failed.
INFINITE -1 Wait forever.

When to Use win32event vs. Other Threading Tools

  • Use threading, multiprocessing, queue, asyncio first: For most standard Python multi-threading or multi-processing needs, the built-in modules are more portable, easier to use, and integrate well with the Python ecosystem. threading.Lock is the direct Python equivalent of a win32event.Mutex.

  • Use win32event when:

    • You need inter-process communication (IPC). Named events/mutexes/semaphores are perfect for this.
    • You are integrating deeply with legacy Windows C++ code that already uses these Win32 primitives.
    • You need very fine-grained, low-level control over thread scheduling that the Python threading module doesn't provide.
    • You need to interface with Windows-specific services that signal via these objects.

In summary, win32event is a powerful and low-level tool for synchronization on Windows. While often overkill for simple tasks, it's indispensable for complex applications requiring robust IPC or deep integration with the Windows OS.

分享:
扫描分享到社交APP
上一篇
下一篇