長い操作を実行するメソッドを持つクラスを含む.NETクラスライブラリがあります。クライアントがこのメソッドを呼び出すとき、呼び出し元のブロックを回避するために、新しいスレッドで長い操作を実行する必要があります。ただし、メソッドが終了すると、メインスレッドでコードを実行する必要があります。 WinFormsアプリケーションでは、System.Windows.Forms.Control.Invokeメソッドを使用できましたが、これは私の場合ではありません。では、どうすればC#でこれを実現できますか?
私は問題の簡単な解決策を見つけました:
私の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>
System.Windows.Threading.Dispatcher
オブジェクト(WindowsBaseアセンブリから)を使用して、特定のスレッドで関数を呼び出すことができます。
例えば:
public class ClassCreatedBySomeThread
{
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
public void SafelyCallMeFromAnyThread(Action a)
{
dispatcher.Invoke(a);
}
}
スレッドが別のスレッドによって投稿されたコード(通常はデリゲートの形式)を実行できる必要がある場合、基本的にそれらの命令を待機する必要があります。あなたのメインスレッドは他に何をしていますか?イベントループに相当するものを構築することはそれほど難しくありませんが(基本的にはデリゲートのプロデューサー/コンシューマーキューがあります)、メインスレッドを中断して「今すぐ実行」と言うことはできないことに注意する必要があります。
なぜこれをメインスレッドで実行する必要があるのですか?
特定のスレッドでコードを明示的に実行する方法はありません(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を返す場合、ActiveXオブジェクトは「BeginInvoke()」メソッドも公開します。次に、同じ関数が入力された別のデリゲートを作成し、Active-XオブジェクトでBeginInvokeを呼び出して、この新しいデリゲートを渡します...次に、Active-Xオブジェクトの作成に使用されたのと同じスレッドで実行されます。
If (MyActiveXObject.InvokeRequired())
MyActiveXObject.BeginInvoke(...);
スレッドは、別のスレッドで何かを実行することはできません。最も近い方法は、他のスレッドが実行するためにデリゲートをキューに配置することですが、これは他のスレッドがこれについて協力していることを前提としています。
WinFormsアプリケーションでは、メインループは、ループの反復ごとにそのようなキューに入れられたメッセージを探します。
ワーカースレッドの終了を通知する必要がある場合は、たとえば、旗のバリエーション。メインスレッドがジョブの終了を待機できる必要がある場合は、セマフォまたは条件変数(モニター)を使用します。
正確なシナリオは次のとおりです。COMオブジェクトとして公開されている.NETクラスライブラリがあります(regasm.exeを使用)。 COMオブジェクトには、(Process.Startを使用して)外部アプリケーションを実行するメソッドが含まれています。 COMオブジェクトはInternetExplorerで使用されます。したがって、私のWebページは外部アプリケーションを実行し、ExitCodeをWebページに渡す方法を見つける必要があります。
最初は、新しいスレッドで外部アプリケーションを起動せず、ユーザーがアプリケーションを閉じるのを待った後、関数がExitCodeを呼び出し元に返しました。しかし、アプリケーションの実行中にIEが応答しませんでした。そのため、新しいスレッドでアプリケーションを開始することにしましたが、ExitCodeを返すことができなくなりました。
COMオブジェクトは次のように作成されます:new ActiveXObject
命令と残念ながらjavascriptはイベントシンクをサポートしていないため、アプリケーションの終了時にトリガーされるイベントをC#で記述できません。