【发布时间】:2008-11-06 19:40:47
【问题描述】:
我有一个 .NET 类库,其中包含一个具有执行一些冗长操作的方法的类。当客户端调用此方法时,它应该在新线程上执行冗长的操作,以避免阻塞调用者。但是一旦方法完成,它应该在主线程上执行一些代码。在 WinForms 应用程序中,我可以使用 System.Windows.Forms.Control.Invoke 方法,但这不是我的情况。那么如何在 C# 中实现这一点呢?
【问题讨论】:
标签: c# multithreading
我有一个 .NET 类库,其中包含一个具有执行一些冗长操作的方法的类。当客户端调用此方法时,它应该在新线程上执行冗长的操作,以避免阻塞调用者。但是一旦方法完成,它应该在主线程上执行一些代码。在 WinForms 应用程序中,我可以使用 System.Windows.Forms.Control.Invoke 方法,但这不是我的情况。那么如何在 C# 中实现这一点呢?
【问题讨论】:
标签: c# multithreading
您可以使用System.Windows.Threading.Dispatcher 对象(来自WindowsBase 程序集)在特定线程上调用函数。
例如:
public class ClassCreatedBySomeThread
{
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
public void SafelyCallMeFromAnyThread(Action a)
{
dispatcher.Invoke(a);
}
}
【讨论】:
我找到了一个简单的解决方案:
我的 COM 对象是这样声明的:
public class Runner
{
public void Run(string executable, object processExitHandler)
{
ThreadPool.QueueUserWorkItem(state =>
{
var p = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = executable
}
};
p.Start();
while (!p.HasExited)
{
Thread.Sleep(100);
}
state
.GetType()
.InvokeMember(
"call",
BindingFlags.InvokeMethod,
null,
state,
new object[] { null, p.ExitCode }
);
}, processExitHandler);
}
}
在我的 HTML 页面中,我这样使用它:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head><title>ActiveXRunner</title>
<script type="text/javascript">
function runNotepad() {
var ax = new ActiveXObject('ActiveXRunner.Runner');
ax.Run('c:\\windows\\notepad.exe', h);
}
function h(exitCode) {
alert('exitCode = ' + exitCode);
}
</script>
</head>
<body>
<a href="#" onclick="runNotepad();">Run notepad and show exit code when finished</a>
</body>
</html>
【讨论】:
如果一个线程必须能够执行由另一个线程发布给它的一些代码(通常以委托的形式),它基本上必须等待这些指令。你的主线程还在做什么?构建一个等效的事件循环并不难(基本上你会有一个生产者/消费者代表队列),但你需要知道你不能只是中断主线程并说“现在就做”。
为什么必须在主线程上执行?
【讨论】:
没有办法显式地使代码在特定线程上运行(用于创建 UI 控件的线程除外 - 这是一个例外),但如果您只是想在线程完成时调用代码以外的代码, 你使用委托。
首先声明一个委托,带有你想在新线程上运行的方法的签名......
public delegate bool CheckPrimeDelegate(long n);
然后在您的代码中,创建一个委托实例,使用 BeginInvoke 调用它(传递它需要的任何参数)并传递一个回调函数委托 (OnChkPrimeDone)
class MyClassApp
{
static void Main()
{
CheckPrimeDelegate ckPrimDel = new CheckPrimeDelegate(Prime.Check);
// Initiate the operation
ckPrimDel.BeginInvoke(4501232117, new AsyncCallback(OnChkPrimeDone), null);
// go do something else . . . .
}
static void OnChkPrimeDone( IAsyncResult iAr)
{
AsyncResult ar = iAr as AsynchResult;
CheckPrimeDelegate ckPrimDel = ar.AsyncDelegate as CheckPrimeDelegate;
bool isPrime = ckPrimDel.EndInvoke(ar);
Console.WriteLine(" Number is " + (isPrime? "prime ": "not prime");
}
}
完成后会调用回调函数(OnChkPrimeDone)
如果您明确需要在用于创建 COM Active-X 对象的线程上运行此回调函数,请检查保存对该对象的引用的 .Net 托管代码包装器变量...如果它有调用 InvokeRequired() 方法,然后在您的回调函数中测试此方法的布尔返回值。
如果它有 InvokeRequired() 方法并且它返回 true,那么 active-X 对象也将公开一个“BeginInvoke()”方法。然后,创建另一个委托,填充相同的函数,并调用 BeginInvoke
在 Active-X 对象上,将这个新委托传递给它...然后它将在用于创建 Active-X 对象的同一线程上运行
If (MyActiveXObject.InvokeRequired())
MyActiveXObject.BeginInvoke(...);
【讨论】:
一个线程不能只在另一个线程上执行东西。您可以得到的最接近的方法是将委托放在队列中以供其他线程执行,但这假定其他线程正在为此进行合作。
在 WinForms 应用程序中,主循环在每次循环迭代时查找此类排队消息。
如果您只需要传达工作线程的完成,您可以使用例如一个标志变量。如果主线程应该能够等待作业终止,请使用信号量或条件变量(监视器)。
【讨论】:
这是我的确切场景:我有一个作为 COM 对象公开的 .NET 类库(使用 regasm.exe)。 COM 对象包含一个运行外部应用程序的方法(使用 Process.Start)。 COM 对象用于 Internet Explorer。所以我的网页运行外部应用程序,我需要找到一种方法将 ExitCode 传递给网页。
起初我没有在新线程上启动外部应用程序,只是等待用户关闭应用程序,然后我的函数将 ExitCode 返回给调用者。但是,当应用程序运行时,IE 没有响应。所以我决定在一个新线程中启动应用程序,但现在我无法再返回 ExitCode。
COM 对象是使用:new ActiveXObject 指令创建的,不幸的是 javascript 不支持事件接收,因此我无法在 C# 中编写应用程序退出时触发的事件。
【讨论】: