У меня есть библиотека классов .NET, содержащая класс с методом, который выполняет некоторую длительную операцию. Когда клиент вызывает этот метод, он должен выполнить длительную операцию в новом потоке, чтобы избежать блокировки вызывающего. Но как только метод заканчивается, он должен выполнить некоторый код в основном потоке. В приложении WinForms я мог бы использовать метод System.Windows.Forms.Control.Invoke, но это не мой случай. Итак, как я могу добиться этого в С#?
Как вызвать функцию в родительском потоке в .NET?
Ответ 1
Я нашел простое решение проблемы:
Мой объект 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>
Ответ 2
Вы можете вызвать функцию в определенном потоке с помощью объекта System.Windows.Threading.Dispatcher
(из сборки WindowsBase).
Например:
public class ClassCreatedBySomeThread
{
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
public void SafelyCallMeFromAnyThread(Action a)
{
dispatcher.Invoke(a);
}
}
Ответ 3
Если поток должен иметь возможность выполнить некоторый бит кода (обычно в форме делегата), отправленный ему другим потоком, он должен будет в основном ждать этих инструкций. Что еще делает ваш основной поток? Не так сложно построить эквивалент цикла событий (в основном у вас будет очередь производителей/потребителей делегатов), но вам нужно знать, что вы не можете просто прервать основной поток и сказать "сделайте это сейчас".
Почему это должно выполняться в основном потоке?
Ответ 4
Нет никакого способа явно сделать запуск кода в определенном потоке (кроме потока, который использовался для создания элементов управления UI - это исключение), но если вы просто хотите вызывать код, отличный от кода, когда поток завершается, Вы используете делегатов.
Сначала объявите делегат с сигнатурой метода, который вы хотите запустить в новом потоке...
public delegate bool CheckPrimeDelegate(long n);
Затем в вашем коде создайте экземпляр делегата, вызовите его с помощью BeginInvoke (передайте любые необходимые ему параметры) и PASS делегат функции обратного вызова (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 Managed Code, содержащую ссылку на этот объект... Если у нее есть метод InvokeRequired(), затем в вашей функции обратного вызова проверьте значение логического возврата этого метода.
Если он имеет метод InvokeRequired() и возвращает значение true, то объект active-X также выдает метод "BeginInvoke()". Затем создайте ДРУГОЙ делегат, заполненный той же функцией, и вызовите BeginInvoke
на объекте Active-X, передав ему этот новый делегат... Затем он будет запускаться в том же потоке, который использовался для создания объекта Active-X
If (MyActiveXObject.InvokeRequired())
MyActiveXObject.BeginInvoke(...);
Ответ 5
Нить не может просто выполнять материал в другом потоке. Самое близкое, что вы можете получить, - это поставить делегата в очередь для выполнения другого потока, но предполагается, что другой поток взаимодействует с этим.
В приложении WinForms основной цикл ищет такие сообщения в очереди на каждой итерации цикла.
Если вам просто нужно сообщить о завершении рабочего потока, вы можете использовать, например. флаг variabe. Если основной поток должен ждать завершения задания, используйте семафор или переменную условия (монитор).
Ответ 6
Здесь мой точный сценарий: у меня есть библиотека классов .NET, выставленная как объект COM (с использованием regasm.exe). COM-объект содержит метод, который запускает внешнее приложение (используя Process.Start). COM-объект используется в Internet Explorer. Поэтому моя веб-страница запускает внешнее приложение, и мне нужно найти способ передать ExitCode на веб-страницу.
Сначала я не запускал внешнее приложение в новом потоке и просто ждал, когда пользователь закроет приложение, а затем моя функция вернет ExitCode вызывающему. Но во время работы приложения IE не реагировал. Поэтому я решил запустить приложение в новом потоке, но теперь я больше не могу вернуть ExitCode.
COM-объект создается с помощью инструкции new ActiveXObject
, и, к сожалению, javascript не поддерживает потоки событий, поэтому я не могу записать событие на С#, которое срабатывало при выходе приложения.