我正在编写一个Visual C#程序,它在辅助线程上执行连续的操作循环。偶尔当该线程完成任务时,我希望它触发一个事件处理程序。我的程序执行此操作但触发事件处理程序时,辅助线程等待事件处理程序完成后再继续执行该线程。如何让它继续?这是我目前的结构方式......
class TestClass
{
private Thread SecondaryThread;
public event EventHandler OperationFinished;
public void StartMethod()
{
...
SecondaryThread.Start(); //start the secondary thread
}
private void SecondaryThreadMethod()
{
...
OperationFinished(null, new EventArgs());
... //This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
}
}
此代码是我的某个设备的API的一部分。当触发OperationFinished事件时,我希望客户端应用程序能够执行它所需的任何操作(即相应地更新GUI),而不会导致API操作。
另外,如果我不想将任何参数传递给事件处理程序,那么使用OperationFinished(null, new EventArgs())
我的语法是否正确?
答案 0 :(得分:53)
所以你想以阻止侦听器阻止后台线程的方式引发事件?给我几分钟的时间来举起一个例子;这很简单: - )
我们转到:首先是重要提示!每当您致电BeginInvoke
时,您必须调用相应的EndInvoke
,否则如果被调用的方法引发了异常或返回一个值,然后ThreadPool线程永远不会被释放回池中,导致线程泄漏!
class TestHarness
{
static void Main(string[] args)
{
var raiser = new SomeClass();
// Emulate some event listeners
raiser.SomeEvent += (sender, e) => { Console.WriteLine(" Received event"); };
raiser.SomeEvent += (sender, e) =>
{
// Bad listener!
Console.WriteLine(" Blocking event");
System.Threading.Thread.Sleep(5000);
Console.WriteLine(" Finished blocking event");
};
// Listener who throws an exception
raiser.SomeEvent += (sender, e) =>
{
Console.WriteLine(" Received event, time to die!");
throw new Exception();
};
// Raise the event, see the effects
raiser.DoSomething();
Console.ReadLine();
}
}
class SomeClass
{
public event EventHandler SomeEvent;
public void DoSomething()
{
OnSomeEvent();
}
private void OnSomeEvent()
{
if (SomeEvent != null)
{
var eventListeners = SomeEvent.GetInvocationList();
Console.WriteLine("Raising Event");
for (int index = 0; index < eventListeners.Count(); index++)
{
var methodToInvoke = (EventHandler)eventListeners[index];
methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
}
Console.WriteLine("Done Raising Event");
}
}
private void EndAsyncEvent(IAsyncResult iar)
{
var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
var invokedMethod = (EventHandler)ar.AsyncDelegate;
try
{
invokedMethod.EndInvoke(iar);
}
catch
{
// Handle any exceptions that were thrown by the invoked method
Console.WriteLine("An event listener went kaboom!");
}
}
}
答案 1 :(得分:12)
使用Task Parallel Library,现在可以执行以下操作:
Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
答案 2 :(得分:11)
另外,如果我不想将任何参数传递给事件处理程序,那么使用OperationFinished(null,new EventArgs())我的语法是否正确?
没有。通常,您将其称为:
OperationFinished(this, EventArgs.Empty);
您应始终将对象作为发件人传递 - 它在模式中是预期的(尽管通常会被忽略)。 EventArgs.Empty也比新的EventArgs()更好。
为了在单独的线程中触发它,最简单的选择可能只是使用线程池:
private void RaiseOperationFinished()
{
ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
{
if (this.OperationFinished != null)
this.OperationFinished(this, EventArgs.Empty);
}));
}
话虽如此,在一个单独的线程上发起一个事件应该被彻底记录,因为它可能会导致意外的行为。
答案 3 :(得分:6)
尝试事件委托上的BeginInvoke和EndInvoke方法 - 这些方法立即返回,并允许您使用轮询,等待句柄或回调函数在方法完成时通知您。有关概述,请参阅here;在您的示例中,事件是您将使用的委托
答案 4 :(得分:4)
下面的方法2或方法3可以提供帮助:)
public partial class Form1 : Form
{
private Thread SecondaryThread;
public Form1()
{
InitializeComponent();
OperationFinished += callback1;
OperationFinished += callback2;
OperationFinished += callback3;
}
private void Form1_Load(object sender, EventArgs e)
{
SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
SecondaryThread.Start();
}
private void SecondaryThreadMethod()
{
Stopwatch sw = new Stopwatch();
sw.Restart();
OnOperationFinished(new MessageEventArg("test1"));
OnOperationFinished(new MessageEventArg("test2"));
OnOperationFinished(new MessageEventArg("test3"));
//This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
sw.Stop();
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "\n";
});
}
void callback1(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback2(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback3(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
public event EventHandler<MessageEventArg> OperationFinished;
protected void OnOperationFinished(MessageEventArg e)
{
//##### Method1 - Event raised on the same thread #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// handler(this, e);
//}
//##### Method2 - Event raised on (the same) separate thread for all listener #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// Task.Factory.StartNew(() => handler(this, e));
//}
//##### Method3 - Event raised on different threads for each listener #####
if (OperationFinished != null)
{
foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
{
Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
}
}
}
}
public class MessageEventArg : EventArgs
{
public string Message { get; set; }
public MessageEventArg(string message)
{
this.Message = message;
}
}
}
答案 5 :(得分:0)
查看BackgroundWorker课程。我认为它完全符合你的要求。
编辑: 我想你要问的是,当整个后台任务的一小部分完成时,如何触发事件。 BackgroundWorker提供了一个名为“ProgressChanged”的事件,它允许您向主线程报告整个过程的某些部分已完成。然后,当所有异步工作完成后,它会引发“RunWorkerCompleted”事件。
答案 6 :(得分:0)
我更喜欢定义一个方法,我将其传递给子线程作为更新UI的委托。首先定义一个委托:
public delegate void ChildCallBackDelegate();
在子线程中定义一个委托成员:
public ChildCallbackDelegate ChildCallback {get; set;}
在调用类中定义更新UI的方法。因为它是从一个单独的线程调用的,所以你需要将它包装在目标控件的调度程序中。注意BeginInvoke。在这种情况下,不需要EndInvoke:
private void ChildThreadUpdater()
{
yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
, new System.Threading.ThreadStart(delegate
{
// update your control here
}
));
}
在启动子线程之前,请设置其ChildCallBack属性:
theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);
然后当子线程想要更新父级时:
ChildCallBack();