[英]Fire event from Async component in UI thread
我正在.Net 2.0中構建一個非可視組件。 該組件使用異步套接字(BeginReceive,EndReceive等)。 異步回調在運行時創建的工作線程的上下文中調用。 組件用戶不必擔心多線程(這是主要目標,我想要的)
組件用戶可以在任何線程中創建我的非可視組件(UI線程只是簡單應用程序的通用線程。更嚴重的應用程序可以在任意工作線程中創建組件)。 組件觸發事件,例如“SessionConnected”或“DataAvailable”。
問題:由於Async Callbacks和其中引發的事件,事件處理程序在工作線程上下文中執行。 我想使用一個中間層,它強制事件處理程序在首先創建組件的線程的上下文中執行。
示例代碼(從異常處理等中刪除...)
/// <summary>
/// Occurs when the connection is ended
/// </summary>
/// <param name="ar">The IAsyncResult to read the information from</param>
private void EndConnect(IAsyncResult ar)
{
// pass connection status with event
this.Socket.EndConnect(ar);
this.Stream = new NetworkStream(this.Socket);
// -- FIRE CONNECTED EVENT HERE --
// Setup Receive Callback
this.Receive();
}
/// <summary>
/// Occurs when data receive is done; when 0 bytes were received we can assume the connection was closed so we should disconnect
/// </summary>
/// <param name="ar">The IAsyncResult that was used by BeginRead</param>
private void EndReceive(IAsyncResult ar)
{
int nBytes;
nBytes = this.Stream.EndRead(ar);
if (nBytes > 0)
{
// -- FIRE RECEIVED DATA EVENT HERE --
// Setup next Receive Callback
if (this.Connected)
this.Receive();
}
else
{
this.Disconnect();
}
}
由於Async套接字的性質,使用我的組件的所有應用程序都充滿了“If(this.InvokeRequired){...”,我想要的只是用戶能夠無憂無慮地使用我的組件作為一種丟棄-在。
那么我如何在不要求用戶檢查InvokeRequired的情況下提升事件(或者換句話說,如何強制在與首先發起事件的線程相同的線程中引發的事件)?
我已經閱讀過有關AsyncOperation,BackgroundWorkers,SynchronizingObjects,AsyncCallbacks以及大量其他內容的內容,但這些都讓我頭暈目眩。
我確實提出了這個,肯定是笨拙的“解決方案”,但在某些情況下它似乎失敗了(例如當我通過靜態類從WinForms項目中調用我的組件時)
/// <summary>
/// Raises an event, ensuring BeginInvoke is called for controls that require invoke
/// </summary>
/// <param name="eventDelegate"></param>
/// <param name="args"></param>
/// <remarks>http://www.eggheadcafe.com/articles/20060727.asp</remarks>
protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
if (eventDelegate != null)
{
try
{
Control ed = eventDelegate.Target as Control;
if ((ed != null) && (ed.InvokeRequired))
ed.Invoke(eventDelegate, args);
else
eventDelegate.DynamicInvoke(args);
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType());
Console.WriteLine(ex.Message);
//Swallow
}
}
}
任何幫助,將不勝感激。 提前致謝!
編輯:根據這個線程,我最好的選擇是使用SyncrhonizationContext.Post,但我看不到如何將它應用到我的情況。
好; 所以這是我在閱讀之后得到的結論:
public class MyComponent {
private AsyncOperation _asyncOperation;
/// Constructor of my component:
MyComponent() {
_asyncOperation = AsyncOperationManager.CreateOperation(null);
}
/// <summary>
/// Raises an event, ensuring the correct context
/// </summary>
/// <param name="eventDelegate"></param>
/// <param name="args"></param>
protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
if (eventDelegate != null)
{
_asyncOperation.Post(new System.Threading.SendOrPostCallback(
delegate(object argobj)
{
eventDelegate.DynamicInvoke(argobj as object[]);
}), args);
}
}
}
這里發布的另一個解決方案是正在進行的工作。 這里發布的解決方案似乎(根據MSDN)是迄今為止最好的解決方案。 建議非常非常歡迎。
我似乎找到了我的解決方案:
private SynchronizationContext _currentcontext
/// Constructor of my component:
MyComponent() {
_currentcontext = WindowsFormsSynchronizationContext.Current;
//...or...?
_currentcontext = SynchronizationContext.Current;
}
/// <summary>
/// Raises an event, ensuring the correct context
/// </summary>
/// <param name="eventDelegate"></param>
/// <param name="args"></param>
protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
if (eventDelegate != null)
{
if (_currentcontext != null)
_currentcontext.Post(new System.Threading.SendOrPostCallback(
delegate(object a)
{
eventDelegate.DynamicInvoke(a as object[]);
}), args);
else
eventDelegate.DynamicInvoke(args);
}
}
我還在測試這個,但似乎工作正常。
也許我不理解這個問題,但在我看來,您可以在Async狀態下傳遞對自定義對象的引用。
我把以下例子放在一起來說明;
首先,我們有一個Callback對象。 這有2個屬性 - 一個用於調度動作的控件和一個要調用的動作;
public class Callback
{
public Control Control { get; set; }
public Action Method { get; set; }
}
然后我有一個WinForms項目,在另一個線程上調用一些隨機代碼(使用BeginInvoke),然后在代碼完成執行時顯示一個消息框。
private void Form1_Load(object sender, EventArgs e)
{
Action<bool> act = (bool myBool) =>
{
Thread.Sleep(5000);
};
act.BeginInvoke(true, new AsyncCallback((IAsyncResult result) =>
{
Callback c = result.AsyncState as Callback;
c.Control.Invoke(c.Method);
}), new Callback()
{
Control = this,
Method = () => { ShowMessageBox(); }
});
}
ShowMessageBox方法必須在UI線程上運行,如下所示:
private void ShowMessageBox()
{
MessageBox.Show("Testing");
}
這是你在找什么?
如果您的組件必須始終由同一個線程使用,您可以執行以下操作:
public delegate void CallbackInvoker(Delegate method, params object[] args);
public YourComponent(CallbackInvoker invoker)
{
m_invoker = invoker;
}
protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
if (eventDelegate != null)
{
try
{
if (m_invoker != null)
m_invoker(eventDelegate, args);
else
eventDelegate.DynamicInvoke(args);
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType());
Console.WriteLine(ex.Message);
//Swallow
}
}
}
然后,當您從窗體或其他控件實例化組件時,您可以執行以下操作:
YourComponent c = new YourComponent(this.Invoke);
要在非UI工作線程上對事件進行排隊,它必須具有某種工作排隊機制,然后您可以為具有CallbackInvoker簽名的方法提供在工作線程上對委托進行排隊的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.