ידע בסיסי על STA/MTA ב-COM — מודל השרשור ואיך נמנעים מתקיעה
· עודכן בתאריך: · Go Komura · COM, פיתוח Windows, STA, MTA, ת'רדים
STA/MTA ב-COM הם ידע בסיסי שקשה להימנע ממנו כשעובדים עם Windows או ניגשים ל-COM מתוך .NET. השאלות הנפוצות ביותר בחיפושים הן: למה ת’רד ה-UI הוא STA, מה קורה כשחוצים Apartment, ולמה נוצרת תקיעה.
תוכן עניינים
- 1. קודם המסקנה (במשפט אחד)
- 2. תבניות הקריאה של Apartment Model (תרשים)
- 3. STA (Single-Threaded Apartment)
- 4. MTA (Multi-Threaded Apartment)
- 5. איפה נקבע STA/MTA
- 6. דוגמה קונקרטית לתקיעה שקורית כשטועים ב-STA
- 7. חלוקה גסה בין השימושים
- 8. סיכום
- 9. מקורות
כשמשתמשים ב-COM, אי אפשר להתעלם מהשאלה “באיזה ת’רד זה רץ”. במרכז הנושא נמצא מודל ה-Apartment (STA/MTA). STA/MTA אינם מושג כללי של ת’רדים ב-Windows, אלא מודל שרשור שקובע את כללי הקריאה לאובייקטי COM.
במאמר הזה נסביר את הקשר בין STA, MTA ו-COM באמצעות תרשימים, ונגיע גם להסבר “למה קורית תקיעה”.
flowchart TB
accTitle: מיקומו של Apartment Model
accDescr: תרשים המראה ש-STA/MTA אינם מושג כללי של ת'רדים ב-Windows, אלא שתי הצורות של מודל ה-Apartment שקובע את כללי הקריאה לאובייקטי COM.
com["כללי הקריאה לאובייקטי COM"] --> am["מודל ה-Apartment"]
am --> sta["STA"]
am --> mta["MTA"]
am -.-> note["לא מושג כללי של ת'רדים ב-Windows"]
איור 1: STA/MTA הן שתי הצורות של מודל ה-Apartment, שקובע את כללי הקריאה לאובייקטי COM.
מפת הידע של המאמר
STA (Single-Threaded Apartment) ו-MTA (Multi-Threaded Apartment) של COM הן שתי הצורות של מודל ה-Apartment, שקובע מאיזה ת’רד מותר לקרוא לרכיב. STA הוא Apartment אחד לכל ת’רד, וקריאה שחוצה Apartment עוברת מרשלינג דרך Proxy/Stub. STA שנקרא מת’רד אחר, אם אינו מריץ לולאת הודעות, לא יכול לקבל את העברת הקריאה ונתקע, וגם תבנית שבה מגיע callback במהלך קריאה סינכרונית נוטה ליצור מבנה של קיפאון. MTA משתף Apartment אחד בין כמה ת’רדים, אבל בתמורה דורש מהאובייקט תכנון בטוח לריבוי ת’רדים. המאפיין [STAThread] ב-.NET הוא רק עטיפה שמגדירה את האתחול הזה של ה-Apartment.
flowchart LR
accTitle: מפת הידע של STA/MTA ב-COM
accDescr: תרשים המראה איך מודל ה-Apartment של COM, STA ו-MTA, קשור ללולאת ההודעות, למרשלינג ול-Proxy/Stub, ואיך היעדר לולאת הודעות מוביל לתקיעה ולקיפאון ב-STA.
sta["STA(Single-Threaded Apartment)"]
mta["MTA(Multi-Threaded Apartment)"]
com["COM (Component Object Model)"]
com_apartment_model["מודל ה-apartment של COM (STA/MTA)"]
coinitializeex["CoInitializeEx"]
message_loop["לולאת ההודעות"]
proxy_stub["Proxy/Stub"]
thread_safe_com_object["תכנון אובייקט COM בטוח לריבוי ת'רדים"]
windows_forms["Windows Forms"]
stathread_attribute["המאפיינים [STAThread]/[MTAThread] ב-.NET"]
threadingmodel_registry["ערך הרישום ThreadingModel"]
com_automation["Automation"]
idispatch["IDispatch"]
midl["MIDL"]
sta_hang["תקיעת STA (עצירת העברת הקריאות)"]
msgwaitformultipleobjects["MsgWaitForMultipleObjects"]
sta_callback_deadlock["קיפאון עקב callback במהלך קריאה סינכרונית"]
dotnet[".NET (מ-Core ואילך)"]
com_marshaling["מרשלינג (marshaling)"]
com -->|"משתמש ב"| com_apartment_model
com_apartment_model -->|"מחייב"| coinitializeex
sta -->|"מחייב"| message_loop
sta -.->|"משתמש ב"| proxy_stub
mta -->|"מחייב"| thread_safe_com_object
windows_forms -.->|"משתמש ב"| sta
windows_forms -->|"משתמש ב"| message_loop
stathread_attribute -->|"משתמש ב"| coinitializeex
threadingmodel_registry -.->|"מחייב"| sta
com_automation -->|"משתמש ב"| idispatch
proxy_stub -.->|"מוגדר באמצעות"| midl
sta -.->|"עלול לגרום ל"| sta_hang
message_loop -->|"מונע"| sta_hang
msgwaitformultipleobjects -.->|"מונע"| sta_hang
sta -.->|"עלול לגרום ל"| sta_callback_deadlock
dotnet -.->|"עלול לגרום ל"| sta_hang
mta -.->|"משתמש ב"| proxy_stub
stathread_attribute -->|"מחייב"| com_apartment_model
proxy_stub -->|"מממש את"| com_marshaling
com_automation -->|"מממש את"| com_marshaling
בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 20, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle
1. קודם המסקנה (במשפט אחד)
- לאובייקט COM נקבעים כללי הקריאה לפי “לאיזה Apartment הוא שייך”
- קל יותר להבין את STA כ-Apartment אחד לכל ת’רד, ואת MTA כ-Apartment אחד המשותף לכמה ת’רדים
- קריאה שחוצה Apartment COM מבצע לה מרשלינג דרך Proxy/Stub
flowchart TB
accTitle: הקשר בין STA, MTA וחציית Apartment
accDescr: תרשים המראה ש-STA הוא Apartment אחד לכל ת'רד, ו-MTA הוא Apartment אחד המשותף לכמה ת'רדים, וקריאה שחוצה Apartment עוברת מרשלינג דרך Proxy/Stub על ידי COM.
sta["STA(Apartment אחד לכל ת'רד)"] -->|"קריאה שחוצה Apartment"| m["מרשלינג דרך Proxy / Stub"]
mta["MTA(Apartment אחד לכמה ת'רדים)"] -->|"קריאה שחוצה Apartment"| m
איור 2: ה-Apartment שאליו האובייקט שייך קובע את כללי הקריאה, ומרשלינג נכנס לתמונה רק כשחוצים Apartment.
2. תבניות הקריאה של Apartment Model (תרשים)
לקריאה לאובייקט COM יש בגדול שלוש תבניות.
flowchart TB
accTitle: שלוש תבניות הקריאה
accDescr: תרשים המראה שלקריאה לאובייקט COM יש שלוש תבניות - קריאה בתוך אותו ת'רד STA, קריאה בתוך אותו MTA, וקריאה שחוצה Apartment.
caller["קריאה לאובייקט COM"] --> p1["תבנית 1: בתוך אותו ת'רד STA"]
caller --> p2["תבנית 2: בתוך אותו MTA"]
caller --> p3["תבנית 3: חוצה Apartment"]
איור 3: יש שלוש תבניות קריאה, וההשתייכות לאחת מהן קובעת את התקורה ואת נקודות תשומת הלב.
2.1. תבנית 1: קריאה בתוך אותו ת’רד STA
בתוך אותו ת’רד STA אפשר לקרוא ישירות. אין תקורה.
flowchart LR
accTitle: קריאה ישירה בתוך ת'רד STA
accDescr: תרשים המראה שבתוך אותו ת'רד STA, הקוד הקורא קורא ישירות לאובייקט COM ללא תקורה.
subgraph STA["ת'רד STA"]
Caller["קוד קורא"]
Obj["אובייקט COM"]
Caller -->|"קריאה ישירה"| Obj
end
איור 4: קריאה בתוך אותו ת’רד STA היא קריאה ישירה, ללא תקורה.
2.2. תבנית 2: קריאה בתוך אותו MTA
מכמה ת’רדים בתוך MTA אפשר לקרוא ישירות מכל ת’רד. אבל צד האובייקט חייב תכנון בטוח לריבוי ת’רדים.
flowchart LR
accTitle: קריאה ישירה בתוך MTA משותף
accDescr: תרשים המראה שבתוך MTA אחד, שני ת'רדי עבודה קוראים ישירות לאותו אובייקט COM, מה שמחייב תכנון בטוח לריבוי ת'רדים בצד האובייקט.
subgraph MTA["MTA - Apartment אחד"]
Thread1["ת'רד עבודה 1"]
Thread2["ת'רד עבודה 2"]
Obj["אובייקט COM"]
Thread1 -->|"קריאה ישירה"| Obj
Thread2 -->|"קריאה ישירה"| Obj
end
איור 5: בתוך אותו MTA אפשר לקרוא ישירות מכל ת’רד, אבל האובייקט חייב תכנון בטוח לריבוי ת’רדים.
2.3. תבנית 3: קריאה שחוצה Apartment
בין Apartment שונים, COM מעביר את הקריאה באמצעות Proxy/Stub. עבור ממשקים סטנדרטיים, זמן הריצה של COM מטפל בזה בשבילכם.
מכאן ואילך מופיעות בטבלה מילים ייחודיות ל-COM בלי הסבר. נסדר אותן קודם, שורה אחר שורה.
| מונח | משמעות |
|---|---|
| מרשלינג | סידור מחדש של הקריאה והארגומנטים לצורה שאפשר “להעביר כמות שהיא” כדי לחצות גבול של Apartment או תהליך. בצד השני של הגבול מוחזרים לצורתם המקורית |
| Proxy / Stub | זוג רכיבים שאחראים על המרשלינג. Proxy עומד בצד הקורא (מעמיד פנים שהוא האובייקט האמיתי ומקבל את הקריאה), Stub עומד בצד הנקרא (מעביר את הקריאה שהתקבלה לאובייקט האמיתי) |
IDispatch |
ממשק COM שמאפשר לשאול שם שיטה כמחרוזת ולקרוא לה לפי מספר. בזכות המנגנון הזה אפשר להשתמש ב-COM משפות סקריפט או מ-VBA |
| Automation | שם כולל לשימוש ב-COM שמניח IDispatch וטיפוסי נתונים מוגבלים שאפשר להשתמש בהם שם (BSTR, VARIANT וכדומה). כשנשארים בטווח הזה, את המרשלינג מבצע oleaut32.dll שבצד מערכת ההפעלה |
| ספריית טיפוסים (Type Library) | נתונים שכותבים בהם את צורת הממשק (שיטות, טיפוסי ארגומנטים) בצורה קריאה למכונה. מונחים כקובץ .tlb נפרד, או מוטמעים בתוך DLL / EXE |
| Type Library Marshaler | פונקציונליות סטנדרטית ב-COM שקוראת ספריית טיפוסים ומבצעת מרשלינג במקום. בזכות זה לא צריך ליצור Proxy/Stub ייעודי |
| MIDL | מהדר של Microsoft שיוצר, בין השאר, קוד Proxy/Stub מתוך הגדרת ממשק (.idl) |
שימו לב: לא הכול נוצר אוטומטית עבור Proxy/Stub, אבל בפועל ברוב המקרים אין צורך ליצור אותם במפורש.
| תבנית | הכנת Proxy/Stub |
|---|---|
מבוסס IDispatch (Automation) |
לא נדרש. oleaut32.dll מטפל בזה |
| ספריית טיפוסים רשומה | לא נדרש. ה-Type Library Marshaler מטפל בזה |
| COM Interop ב-.NET | בדרך כלל לא נדרש. פועל דרך ספריית טיפוסים |
ממשק מותאם-אישית שיורש ישירות מ-IUnknown |
נדרשת יצירה ורישום של Proxy/Stub עם MIDL |
כלומר, יצירת Proxy/Stub עם MIDL נדרשת רק כשבונים ממשק שיורש ישירות מ-IUnknown בלי להשתמש ב-IDispatch.
ברכיבי COM רגילים שמשתמשים בהם מ-.NET או משפות סקריפט, נדרשת העבודה הזו לעיתים רחוקות.
flowchart TB
accTitle: נקודת ההכרעה אם להכין Proxy/Stub בעצמכם
accDescr: תרשים המראה שאם מרשלינג סטנדרטי כמו IDispatch או Type Library Marshaler מספיק, אין צורך ביצירת Proxy/Stub, ורק כשאלה לא מספיקים - עבור ממשק מותאם-אישית שיורש ישירות מ-IUnknown - נדרשת יצירה ורישום עם MIDL.
q{"מרשלינג סטנדרטי מספיק?"} -->|"כן"| auto["אין צורך ביצירת Proxy/Stub"]
q -->|"לא"| midl["יצירה ורישום עם MIDL"]
auto -.-> how["IDispatch או ספריית טיפוסים מטפלים בזה"]
auto -.-> few["ברוב המקרים בפועל זה המצב"]
איור 6: יוצרים Proxy/Stub במפורש רק כשמרשלינג סטנדרטי לא מספיק - עבור ממשק מותאם-אישית שיורש ישירות מ-IUnknown.
flowchart LR
accTitle: מסלול הקריאה כשחוצים Apartment
accDescr: תרשים המראה שקוד קורא בת'רד STA קורא ל-Proxy, שדרך RPC/IPC מעביר ל-Stub שבתוך זמן הריצה של COM, וה-Stub מעביר את הקריאה לאובייקט COM שבת'רד MTA.
subgraph STA["ת'רד STA"]
StaCaller["קוד קורא"]
end
subgraph RT["זמן הריצה של COM - אוטומטי"]
Proxy["Proxy"]
RPC["RPC/IPC"]
Stub["Stub"]
Proxy --> RPC --> Stub
end
subgraph MTA["ת'רד MTA"]
MtaObj["אובייקט COM"]
end
StaCaller -->|"קריאה"| Proxy
Stub -->|"העברה"| MtaObj
איור 7: קריאה שחוצה Apartment עוברת דרך Proxy, RPC ו-Stub שזמן הריצה של COM מכין אוטומטית, ומגיעה ליעד.
נקודה מרכזית: חציית Apartment יוצרת תקורת מרשלינג. בקריאות בתדירות גבוהה יש לזה השפעה על הביצועים, ולכן צריך להביא זאת בחשבון בזמן התכנון.
2.4. אומדן התקורה של המרשלינג
להלן אומדן כללי (לא ערכים נמדדים בפועל, ומשתנה מאוד לפי המצב ומורכבות הפרמטרים).
| תבנית קריאה | זמן משוער | תחושה יחסית |
|---|---|---|
| בתוך אותו Apartment (ישיר) | 10-100 ננו-שנייה | כמעט זהה לקריאת פונקציה רגילה |
| Apartment שונה (אותו תהליך) | 1-10 מיקרו-שנייה | פי 100-1000 מקריאה ישירה |
| תהליך שונה (Out-of-proc) | 100-1000 מיקרו-שנייה | פי 10,000-100,000 מקריאה ישירה |
השוואה יחסית:
- אותו Apartment: בסדר גודל של גישה אחת לזיכרון
- Apartment שונה: בסדר גודל של קריאת מערכת אחת (system call)
- תהליך שונה: בסדר גודל של תקשורת רשת ל-localhost
בתרחיש שבו קוראים 10,000 פעמים בלולאה, ההבדל הזה מורגש מאוד.
לגבי הטיפול במספרים האלה
הטבלה למעלה נועדה לתת תחושה של סדר הגודל, ואינה ערכים נמדדים עם מקור מאומת. אין גם benchmark אחיד שפורסם, ולכן אין להשתמש במספרים האלה כשלעצמם כבסיס להחלטת תכנון.
מצד שני, המבנה שבו “בתוך אותו Apartment יש קריאה ישירה, וחציית גבול תמיד כרוכה במרשלינג” הוא מפרט שכתוב בתיעוד של Microsoft. בפרק על Single-Threaded Apartments כתוב במפורש שבתוך אותו Apartment אפשר להעביר מצביע לממשק בלי מרשלינג, שכאשר חוצים Apartment נעשה שימוש באותו מנגנון מרשלינג כמו בין תהליכים גם אם זה בתוך אותו תהליך, ושהקריאה מגיעה כהודעת חלון לחלון מוסתר (OleMainThreadWndClass). סדר הגודל משתנה בגלל המבנה הזה, והמספרים עצמם משתנים לפי הסביבה ומורכבות הארגומנטים.
אם רוצים להחליט לפי המקרה שלכם, הדרך הבטוחה היא למדוד ולהשוות בין קריאה לאותו ממשק מתוך אותו Apartment לבין קריאה מ-Apartment אחר.
flowchart TB
accTitle: הסיבה המבנית לשינוי בסדר הגודל
accDescr: תרשים המראה שקריאה בתוך אותו Apartment היא קריאה ישירה, בעוד קריאה שחוצה Apartment תמיד כרוכה במרשלינג גם בתוך אותו תהליך, וכשהיעד הוא STA הקריאה מגיעה כהודעת חלון לחלון מוסתר.
same["קריאה בתוך אותו Apartment"] --> direct["קריאה ישירה"]
cross["קריאה שחוצה Apartment"] --> marshal["תמיד כרוכה במרשלינג"]
marshal -.-> msg["ליעד STA מגיעה כהודעה לחלון מוסתר"]
איור 8: השינוי בסדר הגודל נובע מהמבנה שבו חציית גבול תמיד כרוכה במרשלינג.
// C#. חוזרים על אותה קריאה N פעמים ומחשבים את הזמן לקריאה אחת
static void Measure(string label, Action call, int iterations = 100_000)
{
call(); // מוציאים מהמדידה את העיכוב הראשוני (JIT, יצירת proxy, כינון חיבור)
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
call();
}
sw.Stop();
double perCallNs = sw.Elapsed.TotalMilliseconds * 1_000_000.0 / iterations;
Console.WriteLine($"{label}: {perCallNs:F1} ns/call");
}
אם מודדים גם שיטה עם מעט ארגומנטים וגם שיטה שמעבירה מחרוזת או מערך, אפשר לראות ש”ההשפעה משתנה לפי כמות המרשלינג”.
3. STA (Single-Threaded Apartment)
STA הוא מודל של “ת’רד אחד = Apartment אחד”.
- אובייקטי COM בתוך אותו Apartment פועלים בעיקרון רק באותו ת’רד
- קריאה מת’רד אחר COM מעביר אותה דרך תור הודעות/RPC
- נפוץ בת’רד ה-UI (WinForms/WPF) (גם ל-UI יש “זיקה לת’רד אחד + לולאת הודעות”, ולכן ההתאמה טובה)
flowchart TB
accTitle: מודל ההרצה של STA
accDescr: תרשים המראה שאובייקט COM ב-STA פועל בעיקרון רק בת'רד שיצר אותו, וקריאה מת'רד אחר מועברת על ידי COM דרך תור הודעות או RPC לאותו ת'רד.
obj["אובייקט COM ב-STA"] --> own["פועל רק בת'רד שיצר אותו"]
other["קריאה מת'רד אחר"] --> fwd["מועברת דרך תור הודעות / RPC"]
fwd --> own
איור 9: אובייקט STA פועל רק בת’רד הבעלים שלו, וקריאה מבחוץ מגיעה לאחר העברה.
3.1. למה ת’רד ה-UI משתמש ב-STA
מכיוון שהתכנון של ת’רד ה-UI ושל STA תואם.
- פקדי UI אינם בטוחים לריבוי ת’רדים כפתורים ותיבות טקסט וכדומה ניתן לתפעל בבטחה רק מהת’רד שיצר אותם
- גם STA הוא “זיקה לת’רד אחד” אובייקט COM פועל ישירות רק בת’רד שיצר אותו
- ת’רד ה-UI תמיד מריץ לולאת הודעות זה הכרחי לטיפול באירועי חלון. זה תואם לתנאי המוקדם של STA (משאבת ההודעות)
לכן ת’רד ה-UI ב-WinForms/WPF הוא STA כברירת מחדל.
flowchart TB
accTitle: התאמת התכנון בין ת'רד UI ל-STA
accDescr: תרשים המראה שפקדי UI אפשר לתפעל בבטחה רק מהת'רד שיצר אותם, ל-STA יש אותה זיקה לת'רד אחד, ולולאת ההודעות שת'רד ה-UI תמיד מריץ תואמת לתנאי המוקדם של STA - משאבת ההודעות - ולכן ת'רד ה-UI הוא STA כברירת מחדל.
ui["זיקת ת'רד אחד של פקדי UI"] --> match["התאמת תכנון"]
sta["זיקת ת'רד אחד של STA"] --> match
loop["לולאת ההודעות של ת'רד ה-UI"] --> pre["התנאי המוקדם של STA(משאבת הודעות)"]
pre --> match
match --> def["ת'רד ה-UI הוא STA כברירת מחדל"]
איור 10: מכיוון שהתכנון תואם בשתי נקודות - זיקה לת’רד אחד ולולאת הודעות - ת’רד ה-UI הוא STA.
נקודה מרכזית: ל-STA יש זיקה חזקה לת’רד, אבל בתמורה, כשיש הרבה קוראים קל שנוצר בו עומס.
4. MTA (Multi-Threaded Apartment)
MTA הוא מודל של “Apartment אחד לכמה ת’רדים”.
- אובייקט COM נקרא בו-זמנית מכמה ת’רדים
- חובה תכנון בטוח לריבוי ת’רדים בצד האובייקט
- מתאים לעיבוד בצד שרת או עיבוד ברקע
נקודה מרכזית: ל-MTA יש מקביליות גבוהה, אבל האחריות על מימוש האובייקט כבדה יותר.
flowchart TB
accTitle: מודל ההרצה של MTA
accDescr: תרשים המראה ש-MTA משתף Apartment אחד בין כמה ת'רדים, ואובייקט COM נקרא בו-זמנית מכמה ת'רדים, מה שמחייב תכנון בטוח לריבוי ת'רדים בצד האובייקט.
mta["MTA(Apartment אחד לכמה ת'רדים)"] --> par["נקרא בו-זמנית מכמה ת'רדים"]
par --> safe["חובה תכנון בטוח לריבוי ת'רדים בצד האובייקט"]
mta -.-> use["מתאים לעיבוד בצד שרת או ברקע"]
איור 11: ל-MTA יש קריאה מקבילית, אבל האחריות על הבטיחות עוברת למימוש האובייקט.
5. איפה נקבע STA/MTA
Apartment של COM נקבע על ידי אתחול לכל ת’רד.
- ברגע שקוראים ל-
CoInitialize/CoInitializeEx, נקבע ה-Apartment של אותו ת’רד - STA:
COINIT_APARTMENTTHREADED - MTA:
COINIT_MULTITHREADED
flowchart TB
accTitle: הרגע שבו נקבע ה-Apartment
accDescr: תרשים המראה שברגע שהת'רד קורא ל-CoInitialize או ל-CoInitializeEx נקבע ה-Apartment שלו, ו-COINIT_APARTMENTTHREADED קובע STA בעוד COINIT_MULTITHREADED קובע MTA.
th["ת'רד"] --> init["קורא ל-CoInitialize / CoInitializeEx"]
init -->|"COINIT_APARTMENTTHREADED"| sta["נקבע STA"]
init -->|"COINIT_MULTITHREADED"| mta["נקבע MTA"]
איור 12: ה-Apartment נקבע לפי הציון שנעשה ברגע קריאת האתחול, לכל ת’רד בנפרד.
5.1. STA/MTA ב-.NET
גם ל-.NET יש את המאפיינים [STAThread] / [MTAThread] ואת ApartmentState, אבל אלה עטיפות להגדרת מודל ה-Apartment של COM.
[STAThread]→ מוסיפים ל-Main (נקודת הכניסה). מאותחל כ-STA כשמשתמשים ב-COM[MTAThread]→ גם הוא עבור Main. מאותחל כ-MTAThread.SetApartmentState(ApartmentState.STA)→ עבור ת’רדים נוספים שיוצרים. יש להגדיר לפני התחלת הת’רד
נקודות לתשומת לב:
- גם אם יש
[STAThread], האתחול לא מתבצע עד לקריאה בפועל ל-COM (אין השפעה באפליקציה שלא משתמשת ב-COM) [STAThread]לא פועל על ת’רדים נוספים. יש להשתמש ב-Thread.SetApartmentState
כלומר, ה-STA/MTA של .NET הם STA/MTA של COM עצמם, מנגנון שהוכן עבור COM Interop.
חשוב: אי אפשר לשנות את ה-Apartment לאחר מכן. האתחול הראשון קובע הכול.
flowchart TB
accTitle: היכן קובעים Apartment ב-.NET
accDescr: תרשים המראה של-Main מוסיפים מאפיין STAThread או MTAThread, ולת'רדים נוספים שיוצרים מגדירים לפני ההתחלה עם Thread.SetApartmentState, ובשני המקרים ה-Apartment נקבע באתחול הראשון ולא ניתן לשנות אותו לאחר מכן.
main["שיטת Main"] --> attr["מאפיין [STAThread] / [MTAThread]"]
newth["ת'רד נוסף שנוצר"] --> setap["לפני ההתחלה: Thread.SetApartmentState"]
attr --> fixed["האתחול הראשון קובע את ה-Apartment"]
setap --> fixed
fixed -.-> nochange["לא ניתן לשנות לאחר מכן"]
איור 13: בנקודת הכניסה זה נעשה במאפיין, ולת’רד נוסף עם SetApartmentState, ובשניהם ההגדרה נקבעת בפעם הראשונה בלבד.
6. דוגמה קונקרטית לתקיעה שקורית כשטועים ב-STA
מבנה כמו הבא גורם בפועל בקלות לתקיעה.
6.1. מצב נפוץ
- יוצרים ת’רד STA ברקע ומייצרים בו אובייקט COM
- הת’רד הזה אינו מריץ לולאת הודעות
- קוראים לאובייקט ה-COM הזה מת’רד אחר (בלי קשר אם הוא STA או MTA)
flowchart TB
accTitle: מבנה שגורם בקלות לתקיעה
accDescr: תרשים המראה שת'רד STA שנוצר ברקע ייצר אובייקט COM אך אינו מריץ לולאת הודעות, ות'רד אחר קורא לאובייקט COM הזה - מבנה שגורם בקלות לתקיעה.
bg["ת'רד STA ברקע"] --> gen["מייצר אובייקט COM"]
bg --> noloop["אינו מריץ לולאת הודעות"]
other["ת'רד אחר(STA או MTA)"] -->|"קורא"| gen
איור 14: השילוב של “STA שלא מריץ לולאה, שמחזיק אובייקט שקוראים לו מת’רד אחר” מסוכן.
6.2. מה קורה בפועל
את סיבת התקיעה אפשר לצמצם לשני תנאים מוקדמים של STA. הפרק הזה מסביר את הסיבה, ובפרקים הבאים לא נחזור עליה.
- אובייקט COM מטופל בת’רד ה-STA שיצר אותו
בין אם הקורא הוא STA או MTA, קריאה מת’רד אחר תמיד מועברת לאותו ת’רד STA. ההעברה מגיעה כהודעת חלון לחלון מוסתר (מחלקת חלון
OleMainThreadWndClass) ש-COM יוצר עבור אותו Apartment - כדי לקבל את ההעברה, ת’רד ה-STA חייב להריץ משאבת הודעות גם בתיעוד של Microsoft כתוב במפורש ש”כל STA חייב שתהיה לו לולאת הודעות, כדי לטפל בקריאות מתהליכים אחרים או מ-Apartment אחר באותו תהליך”
לכן ת’רד STA שלא מריץ הודעות לא יכול לקבל את הקריאה, הקורא ממשיך להמתין לתשובה, וכתוצאה מכך נוצרת תקיעה.
flowchart TB
accTitle: הזרימה שמובילה לתקיעה
accDescr: תרשים המראה שקריאה מת'רד אחר מועברת לת'רד ה-STA שיצר את האובייקט כהודעה לחלון מוסתר, אך אם ת'רד ה-STA לא מריץ משאבת הודעות הוא לא מקבל אותה, והקורא ממשיך להמתין לתשובה ונתקע.
caller["קריאה מת'רד אחר"] --> fwd["מועברת לת'רד ה-STA שיצר את האובייקט"]
fwd -.-> hidden["מגיעה כהודעה לחלון מוסתר"]
fwd --> nopump["משאבת ההודעות לא רצה"]
nopump --> norecv["צד ה-STA לא מקבל את הקריאה"]
norecv --> wait["הקורא ממשיך להמתין לתשובה"]
wait --> hang["תקיעה"]
איור 15: ההעברה מגיעה כהודעה, ולכן כשהמשאבה עצורה ב-STA אין מי שיקבל אותה.
זה נכון גם ב-.NET. בתיעוד של Single-Threaded Apartments יש אזהרה שאם חוסמים ת’רד STA באמצעות Task.Wait(), Task.Result, Thread.Sleep(), ManualResetEvent.WaitOne() וכדומה, callback של COM או קריאה שחוצה Apartment לא יכולים להסתיים, וזה גורם לקיפאון (deadlock). דוגמת הכשל בסעיף 6.3 הבא, שנעצרת ב-WaitOne(), היא בדיוק הצורה הזו.
מצד שני, ת’רד ה-UI מריץ לולאת הודעות מלכתחילה כדי לטפל באירועי חלון, ולכן הוא ממלא את דרישת STA בלי מימוש נוסף. זו הסיבה שת’רד ה-UI הוא בחירה טבעית להריץ בו אובייקטי COM מסוג STA.
6.3. פסאודו-קוד (תבנית כשל טיפוסית)
using System;
using System.Runtime.InteropServices;
using System.Threading;
internal static class StaHangDemo
{
private const uint COINIT_APARTMENTTHREADED = 0x2;
[DllImport("ole32.dll")]
private static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit);
[DllImport("ole32.dll")]
private static extern void CoUninitialize();
public static void Run(string progId)
{
var ready = new AutoResetEvent(false);
var done = new AutoResetEvent(false);
object comObj = null;
var staThread = new Thread(() =>
{
// אתחול כ-STA
CoInitializeEx(IntPtr.Zero, COINIT_APARTMENTTHREADED);
// בהנחה שמדובר במחלקת COM שנרשמה עם ThreadingModel=Apartment
Type type = Type.GetTypeFromProgID(progId, throwOnError: true);
comObj = Activator.CreateInstance(type);
ready.Set();
// ממתין בלי לולאת הודעות -> כאן נמצא הפגם הקטלני
done.WaitOne();
CoUninitialize();
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
ready.WaitOne();
try
{
// קריאה מת'רד אחר (בין אם STA או MTA) מועברת ל-STA
// אבל צד ה-STA לא מטפל בהודעות, ולכן קל שכאן נוצרת תקיעה
dynamic obj = comObj;
obj.AnyMethod();
}
finally
{
// גם אם הקריאה חוזרת (המחלקה הייתה agile, הקריאה לא הועברה,
// AnyMethod חזרה מיד וכו') - חשוב תמיד לשחרר את ת'רד ה-STA.
// בלי זה, נשאר ת'רד קדמי שממתין ל-done, וגם כש"לא שוחזרה
// התקיעה" התהליך לא מסתיים. אי אפשר להבחין בין שני המצבים
// לפי הסימפטום
done.Set();
staThread.Join();
}
}
}
הקוד הזה מניח שמעבירים ProgID של מחלקת COM שנרשמה כ-ThreadingModel=Apartment (= STA). יש להחליף את AnyMethod בשם שיטה שקיימת בפועל באותה מחלקה. זה לא משוחזר עם כל מחלקת COM. שלושת התנאים הנדרשים לשחזור הם:
| תנאי | איך בודקים |
|---|---|
ה-ThreadingModel של המחלקה הוא Apartment |
בודקים את הערך ThreadingModel תחת HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocServer32 ברג’יסטרי. אם זה Both או Free, הקריאה לא מועברת, ולא ישוחזר |
| ת’רד ה-STA לא מריץ הודעות | זה בדיוק מה ש-done.WaitOne() בדוגמה למעלה עושה |
| הקריאה מתבצעת מת’רד אחר | כל עוד קוראים מתוך אותו ת’רד STA, זו קריאה ישירה, ולא נוצרת תקיעה |
הסיבה ש-obj.AnyMethod() עטוף ב-try / finally, עם done.Set() ו-staThread.Join() תמיד מתבצעים, היא כדי להבחין ב”מקרה שלא שוחזר”. ת’רד STA הוא כברירת מחדל ת’רד קדמי, ולכן התהליך לא מסתיים כל עוד done לא הוגדר. אם משמיטים את ה-finally, הסימפטום שנראה מבחוץ יהיה “התהליך לא מסתיים” גם כשהתקיעה שוחזרה (הקריאה לא חוזרת) וגם כשלא (הקריאה חזרה אבל אף אחד לא הגדיר את done). הכלי שנועד לבדוק את תנאי השחזור מסתיר בעצמו את הצלחת התנאי. עם הצורה למעלה, כשלא משוחזר, התהליך מסתיים כרגיל.
כדי לוודא שיש תקיעה, הדרך המהירה היא לעצור את התהליך בדיבאגר ולבדוק שה-Stack של ת’רד הקורא עצור בהמתנה של COM, ושת’רד ה-STA עצור ב-WaitOne.
sequenceDiagram
accTitle: הזרימה שיוצרת תקיעה בדוגמת STA
accDescr: תרשים רצף המראה שת'רד ה-STA נעצר ב-WaitOne בלי לולאת הודעות, ואילו ת'רד ראשי מנסה לקרוא לאובייקט COM, וההעברה לא מתקבלת, ושני הצדדים ממתינים זה לזה - תקיעה.
participant Main as ת'רד ראשי
participant STA as ת'רד STA
participant COM as זמן הריצה של COM
Main->>STA: התחלת ת'רד
STA->>STA: CoInitializeEx (STA)
STA->>STA: יצירת אובייקט COM
STA->>Main: ready.Set()
STA->>STA: ממתין עם done.WaitOne()
Note over STA: אין לולאת הודעות<br/>תקוע כאן
Main->>COM: CallComObject()
COM->>STA: מנסה להעביר את הקריאה
Note over COM: מעביר בהודעה, אבל...
Note over STA: בגלל WaitOne<br/>לא יכול לטפל בהודעה
Note over Main: גם הקורא ממשיך להמתין
Note over Main,STA: שני הצדדים ממתינים -> תקיעה
איור 16: ת’רד STA שנעצר ב-WaitOne ות’רד ראשי שממתין לתשובת ההעברה - שניהם לא יכולים להתקדם.
במרכז התרשים, שתי השורות “מעביר בהודעה, אבל…” הן המקום שבו שני התנאים המוקדמים שהוזכרו בסעיף 6.2 נשברים.
6.4. עקרונות למניעה
- כשמקבלים קריאות מת’רד אחר, ת’רד ה-STA חייב להריץ לולאת הודעות
- אם אפשר, ליצור ולהשתמש בת’רד ה-UI (לת’רד ה-UI כבר יש לולאת הודעות מלכתחילה)
- אם STA לא נחוץ, להשתמש מלכתחילה ב-MTA
הערה: אם הכול נשאר בתוך אותו ת’רד, לא תמיד Application.Run() הכרחי.
אבל מכיוון שב-UI וב-COM לרוב מעורבת קריאה מת’רד אחר, זה כמעט הכרחי בפועל.
flowchart TB
accTitle: שלושה כיווני מניעה של תקיעה
accDescr: תרשים המראה ששלושת כיווני המניעה של תקיעה הם - להריץ לולאת הודעות בת'רד ה-STA כשמקבלים קריאות מת'רד אחר, ליצור ולהשתמש באובייקט בת'רד ה-UI שיש לו כבר לולאה, ואם STA לא נחוץ - להשתמש מלכתחילה ב-MTA.
q["איך נמנעים מתקיעת STA"] --> a1["להריץ לולאת הודעות בת'רד ה-STA"]
q --> a2["ליצור ולהשתמש בת'רד ה-UI"]
q --> a3["אם STA לא נחוץ - MTA מלכתחילה"]
a2 -.-> why["לת'רד ה-UI כבר יש לולאה"]
איור 17: אפשר לסדר את דרכי המניעה בשלושה כיוונים - להריץ לולאה, לעבור למקום שיש בו לולאה, או לוותר על STA.
6.5. מה זה בעצם “להריץ לולאת הודעות”?
זה בדיוק מה שת’רד ה-UI ב-Win32 עושה.
while (GetMessage(out var msg, IntPtr.Zero, 0, 0))
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
ב-STA, קריאה מת’רד אחר “מועברת” לכאן. הלולאה הזו (משאבת ההודעות) היא זו שמקבלת את ההעברה ומעבירה אותה להרצה.
flowchart TB
accTitle: תפקיד משאבת ההודעות
accDescr: תרשים המראה שמשאבת ההודעות היא החזרה על קבלת הקריאה שהועברה מת'רד אחר באמצעות GetMessage והעברתה להרצה באמצעות DispatchMessage.
fwd["קריאה שהועברה מת'רד אחר"] --> gm["מתקבלת עם GetMessage"]
gm --> dm["מועברת להרצה עם DispatchMessage"]
dm --> gm
איור 18: “להריץ לולאת הודעות” הוא בדיוק החזרה הזו של קבלת ההעברה והעברתה להרצה.
6.6. דוגמה לכיוון נכון (בגדול, ככה)
אם רוצים “להשתמש ב-COM ב-STA שברקע”, זה נראה כך.
var ready = new AutoResetEvent(false);
object comObj = null;
var staThread = new Thread(() =>
{
CoInitializeEx(IntPtr.Zero, COINIT_APARTMENTTHREADED);
comObj = new SomeStaComObject();
ready.Set();
// כל עוד ת'רד ה-STA חי, מריצים הודעות
Application.Run();
CoUninitialize();
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
ready.WaitOne();
CallComObject(comObj);
(לשכוח לקרוא ל-CoInitializeEx / CoUninitialize זו תקלה רגילה. ההצהרה מסוג P/Invoke עבור CoInitializeEx נדרשת זהה לזו שבסעיף 6.3)
הצורה של Application.Run() בלי ארגומנטים נועדה להריץ רק לולאת הודעות בלי טופס. זה בדיוק המקרה שאליו זה מיועד, אבל כדאי לזכור שתי נקודות:
- נדרשת הפניה ל-
System.Windows.Forms. באפליקציית קונסולה, מוסיפים<UseWindowsForms>true</UseWindowsForms>לקובץ הפרויקט - הלולאה הזו לא מסתיימת מעצמה. כדי לעצור אותה, קוראים ל-
Application.ExitThread()(או ל-Application.Exit()שמסיים את כל האפליקציה) מתוך אותו ת’רד. אם רוצים להגיע ל-CoUninitialize()בדוגמה למעלה, צריך מנגנון נפרד שמעביר “סיום” לת’רד ה-STA ומריץ בוExitThread
אם לא רוצים תלות ב-WinForms, אפשר לכתוב בעצמכם לולאה של GetMessage / DispatchMessage כמו בסעיף 6.5, או להשתמש ב-MsgWaitForMultipleObjects, שמאפשר להמתין גם להודעות וגם לאובייקטי סנכרון. זו הבחירה הרגילה כשרוצים “לקבל הודעת סיום דרך אירוע, בלי לפספס קריאות COM”.
flowchart TB
accTitle: שלושה אמצעים להרצת לולאה ב-STA ברקע
accDescr: תרשים המראה ששלושת האמצעים להרצת לולאת הודעות ב-STA ברקע הם Application.Run שדורש הפניה ל-WinForms, כתיבה עצמית של לולאת GetMessage ו-DispatchMessage, או שימוש ב-MsgWaitForMultipleObjects שממתין גם להודעות וגם לאובייקטי סנכרון.
want["הרצת לולאה ב-STA ברקע"] --> ar["Application.Run"]
want --> gml["כתיבה עצמית של לולאת GetMessage"]
want --> mw["MsgWaitForMultipleObjects"]
ar -.-> dep["דורש הפניה ל-WinForms ואמצעי סיום"]
mw -.-> both["ממתין גם להודעות וגם לאובייקטי סנכרון"]
איור 19: יש שלוש דרכים להריץ לולאה, ובוחרים לפי אופן הסיום ומשקל התלות.
6.7. עוד דוגמת תקיעה: callback במהלך קריאה סינכרונית
ל-STA לא רק “מעבירים אליו קריאות” - במצבים מסוימים מגיע גם callback בכיוון ההפוך (משרת ללקוח). בפרט, תבנית שבה callback קורה במהלך קריאה סינכרונית היא דוגמה קלאסית לקיפאון (deadlock).
sequenceDiagram
accTitle: קיפאון בין קריאה סינכרונית ל-callback
accDescr: תרשים רצף המראה שת'רד ה-UI ממתין לתשובה מקריאה סינכרונית ולא מטפל בהודעות, השרת שולח callback שהוא לא יכול לקבל, והשרת מצדו ממתין לסיום ה-callback - שני הצדדים ממתינים זה לזה.
participant UI as ת'רד UI (STA)
participant Server as שרת COM
UI->>Server: DoWork() (קריאה סינכרונית)
Note over UI: ממתין לתשובה של DoWork<br/>(לא מטפל בהודעות)
Server->>UI: ProgressCallback() (callback)
Note over UI: בגלל שממתין,<br/>לא יכול לקבל את ה-callback
Note over Server: ממתין לסיום ה-callback
Note over UI,Server: שני הצדדים ממתינים זה לזה -> קיפאון
איור 20: ת’רד UI שממתין לתשובה של קריאה סינכרונית, ושרת שממתין לסיום callback - ממתינים זה לזה.
למה קל שנוצר קיפאון:
- ת’רד ה-UI קורא ל-
DoWork()בקריאה סינכרונית (חוסמת) - ת’רד ה-UI ממתין לתשובה (לא מטפל בהודעות)
- השרת שולח
ProgressCallback()לת’רד ה-UI - מכיוון שת’רד ה-UI ממתין, הוא לא יכול לקבל את ה-callback
- השרת ממתין לסיום ה-callback
- שני הצדדים ממתינים זה לזה -> לא מתקדם לעולם
אורך זמן העיבוד לא רלוונטי. התבנית עצמה שבה callback מגיע במהלך קריאה סינכרונית נוטה להיות בעייתית.
הערה: ל-COM יש גם מנגנוני הרצת הודעות וכניסה חוזרת (reentrancy) שמשתנים לפי המצב, וההתנהגות משתנה לפי הרכיב וצורת הקריאה. זה לא תמיד גורם לקיפאון, אבל עדיף להימנע מהתבנית הזו.
7. חלוקה גסה בין השימושים
| מצב | מה בוחרים | סיבת ההחלטה | מה נדרש גם |
|---|---|---|---|
| מעורב UI (WinForms / WPF) | STA | לפקדי UI יש זיקה לת’רד אחד, ולת’רד ה-UI כבר יש לולאת הודעות מלכתחילה (3.1) | כלום במיוחד. STA כברירת מחדל |
| רוצים הרבה עיבוד מקבילי | MTA | כמה ת’רדים משתפים Apartment אחד וקוראים ישירות בלי העברה (2.2) | תכנון בטוח לריבוי ת’רדים בצד אובייקט ה-COM. זו אחריות צד המימוש של האובייקט, לא חסימה בצד הקורא |
| רוצים להשתמש ב-COM מסוג STA ברקע | STA + לולאת הודעות | כל עוד קוראים מת’רד אחר, נדרש פתח לקבלת ההעברה (6.2) | Application.Run() או לולאת GetMessage. יש לתכנן גם אמצעי סיום (כמו ExitThread) (6.6) |
| הדרישה של רכיב ה-COM שבו משתמשים כבר קבועה | מתאימים לצד השני | ה-Apartment הוא כלל הקריאה עצמו, ואי אפשר לשנות אותו לאחר מכן (פרק 5) | בודקים את הערך ThreadingModel. אם זה Apartment, בונים בהנחת STA |
| קריאה בתדירות גבוהה | מתקרבים לאותו Apartment של הקורא | בכל חציית גבול נכנס מרשלינג (2.4) | אם קשה - לתכנן צמצום של מספר הקריאות עצמו (העברה מרוכזת) |
כשמתלבטים, קל יותר להחליט לפי סדר העדיפויות “דרישת הצד השני > קיום UI > מקביליות”. מכיוון שה-Apartment נקבע באתחול הראשון ולא ניתן לשנות אותו אחר כך, כדאי להחליט על זה עוד לפני תחילת המימוש.
flowchart LR
accTitle: הסדר שבודקים כשמתלבטים
accDescr: תרשים המראה שכשמתלבטים בין STA ל-MTA, קל יותר להחליט אם בודקים לפי הסדר - דרישת רכיב ה-COM שבו משתמשים, קיום UI, ומקביליות.
p1["דרישת הצד השני"] -->|"אחר כך"| p2["קיום UI"]
p2 -->|"אחר כך"| p3["מקביליות"]
איור 21: כשמתלבטים בין השימושים, בודקים לפי הסדר - דרישת הצד השני, קיום UI, ומקביליות.
8. סיכום
STA/MTA הם מודל שרשור עבור COM: STA הוא ת’רד אחד = Apartment אחד, ו-MTA הוא Apartment אחד המשותף לכמה ת’רדים. קריאה שחוצה Apartment מועברת על ידי COM דרך Proxy/Stub (עבור ממשקים שאינם סטנדרטיים נדרשת יצירה ורישום, למשל עם MIDL), אבל זה כרוך בתקורת מרשלינג, ולכן במקומות שבהם צפויות קריאות בתדירות גבוהה כדאי להחליט בזהירות על תכנון ה-Apartment.
מבחינת תקיעה, הכול מצטמצם לנקודה אחת: “ת’רד STA שמקבל קריאות מת’רד אחר חייב להריץ משאבת הודעות”. קריאה לת’רד STA שלא מריץ הודעות נוטה לגרום לתקיעה, וגם תבנית שבה מגיע callback במהלך קריאה סינכרונית נוטה לגרום לקיפאון. לת’רד ה-UI יש מלכתחילה גם “זיקה לת’רד אחד” וגם “לולאת הודעות”, ולכן הוא ממלא את התנאי הזה בלי מימוש נוסף, וזו הסיבה שהוא מתאים כל כך ל-COM מסוג STA.
9. מקורות
- Apartment Model https://learn.microsoft.com/en-us/windows/win32/com/com-apartments
- CoInitializeEx https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitializeex
- Single-Threaded Apartments (הסיבה שלולאת הודעות הכרחית, החלון המוסתר, אזהרת קיפאון ב-.NET) https://learn.microsoft.com/en-us/windows/win32/com/single-threaded-apartments
- Multithreaded Apartments https://learn.microsoft.com/en-us/windows/win32/com/multithreaded-apartments
- InprocServer32 (הערך
ThreadingModel) https://learn.microsoft.com/en-us/windows/win32/com/inprocserver32 - שיטת Application.Run (הצורה של הרצת לולאת הודעות בלי טופס, ואיך עוצרים אותה) https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.application.run
- MsgWaitForMultipleObjects (המתנה בו-זמנית להודעות ולאובייקטי סנכרון) https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjects
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
מה זה Reg-Free COM - שימוש ב-COM בלי רישום
המאמר מסדר את היסודות של Reg-Free COM - תפקיד ה-activation context וה-manifest, היתרונות, המגבלות, וקריטריוני ההחלטה בפועל.
בניית פלט דוחות Excel - COM, Open XML, תבנית
פלט דוחות Excel משתנה מהותית לפי השאלה אם מפעילים את Excel אוטומטית, יוצרים xlsx ישירות, או משמרים VBA קיים. המאמר מסדר את קריטריוני הבחי...
מה זה COM / ActiveX / OCX — הסבר מרוכז על ההבדלים והקשרים
מהו COM, מהו ActiveX ומהו OCX — סקירה מעשית של ההבדלים והקשרים ביניהם, הזיקה ל-OLE, איפה זה נמצא בשימוש, ואיך כדאי להתייחס לזה היום.
איך מתייחסים היום ל-ActiveX / OCX — טבלת החלטה: לשמר, לעטוף או להחליף
כשמוצאים ActiveX / OCX, מסודר כאן איך לבחור בין לשמר, לעטוף או להחליף — כולל 32bit / 64bit, רישום, תלות בדפדפן ותחזוקת ספקים.
מבוא ל-Media Foundation — מבינים את ה-API מנקודת המבט של COM
המאמר מסביר מה זה Media Foundation, יחד עם המונחים הבסיסיים של ה-API למדיה ב-Windows כמו COM, HRESULT, IMFSourceReader ו-MFT, בסדר שכדא...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
ייעוץ טכני וסקירת תכנון
סידור הנושאים של STA / MTA, לולאת הודעות ומרשלינג מתחבר ישירות לחלוקת אחריות לפני המימוש ולסקירת גבולות ת'רדים.
שימוש חוזר והעברה של נכסים קיימים
זהו ידע בסיסי שקשה להימנע ממנו כשמטפלים בנכסים קיימים הכוללים COM, ולכן זה מתאים גם לייעוץ בנושא מינוף נכסים קיימים ותמיכה במעבר.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- האם כדאי לבחור STA או MTA?
- בעיבוד שקשור ל-UI משתמשים ב-STA, ובעיבוד מקבילי בהיקף גדול משתמשים ב-MTA — זו החלוקה הבסיסית. ל-STA יש זיקה חזקה לת'רד אחד (Apartment אחד לת'רד), אך כאשר יש הרבה קוראים היא נוטה להיווצר בה עומס. MTA משתף Apartment אחד בין כמה ת'רדים, כך שיש בו מקביליות גבוהה, אך זה מחייב תכנון בטוח לריבוי ת'רדים בצד אובייקט ה-COM. אם המצב אינו אחד מהם, מעשי להתאים לדרישה של הספרייה הקיימת או שרת ה-COM שבו משתמשים.
- למה ת'רד ה-UI הוא STA?
- מכיוון שהתכנון של ת'רד ה-UI ושל STA תואם. פקדי UI כמו כפתורים ותיבות טקסט אינם בטוחים לריבוי ת'רדים, ואפשר לתפעל אותם בבטחה רק מהת'רד שיצר אותם. גם STA הוא מודל של זיקה לת'רד אחד. בנוסף, ת'רד ה-UI תמיד מריץ לולאת הודעות כדי לטפל באירועי חלון, וכך הוא ממלא ללא מימוש נוסף את התנאי המוקדם של STA — משאבת ההודעות. לכן ת'רד ה-UI ב-WinForms/WPF הוא STA כברירת מחדל.
- למה קוראים לאובייקט COM ב-STA וזה תוקע?
- קריאה לאובייקט COM ב-STA מטופלת בת'רד ה-STA שיצר אותו. קריאה מת'רד אחר מועברת על ידי COM דרך הודעה/RPC, אך אם ת'רד ה-STA אינו מריץ לולאת הודעות הוא לא יכול לקבל את ההעברה, והקורא ממשיך להמתין ונתקע. כדי להימנע מזה, יש להריץ לולאת הודעות בת'רד ה-STA שנקרא מת'רד אחר, או ליצור ולהשתמש בו בת'רד ה-UI, או אם STA לא נחוץ — להשתמש מלכתחילה ב-MTA.
- לשם מה קיים המאפיין [STAThread] ב-.NET?
- זוהי עטיפה שמגדירה את מודל ה-Apartment של COM. כשמוסיפים אותה ל-Main, הת'רד מאותחל כ-STA כאשר משתמשים ב-COM. עם זאת, האתחול לא מתבצע עד לקריאה בפועל ל-COM, ולכן אין לה השפעה באפליקציה שלא משתמשת ב-COM. היא גם לא פועלת על ת'רדים נוספים שנוצרים, ולכן משתמשים ב-Thread.SetApartmentState לפני התחלת הת'רד. חשוב לזכור גם ש-Apartment נקבע באתחול הראשון ולא ניתן לשנות אותו לאחר מכן.