Альтернатива Thread.Abort для процессора + интенсивный по времени метод
У меня есть вопрос о многопоточности приложений. Я использую TaskFactory, чтобы запустить процессор + интенсивный по времени метод. Этот метод является вызовом SAP и требует длительного времени для завершения. У пользователя должна быть возможность отменить задачу. В настоящее время я использую thread.Abort(), но я знаю, что этот метод не является лучшим решением для его отмены. У кого-нибудь есть рекомендации по альтернативе?
Пример кода:
Form_LoadAction loadbox = new Form_LoadAction();
Thread threadsapquery = null;
Task.Factory.StartNew<>(() =>
{
t = Thread.CurrentThread;
Thread.sleep(10000000000); //represents time + cpu intensive method
}
loadbox.ShowDialog();
if (loadbox.DialogResult == DialogResult.Abort)
{
t.Abort();
}
2 ответа
Лучший вариант - посмотреть, поддерживает ли метод какой-либо вид совместной отмены.
Однако, если это невозможно, следующий лучший способ отменить длительный процесс, подобный этому, - это использовать второй исполняемый файл, который запускает длительный процесс, а затем обмениваться данными с этим вторым исполняемым файлом через какую-то форму IPC (WCF через Named Pipes прекрасно работает для -машина IPC) для "прокси" всех звонков. Когда вам нужно отменить ваш процесс, вы можете убить 2-й прокси-exe, и все дескрипторы будут правильно освобождены (где Thread.Abort()
не будет).
Вот полный пример. Существует 3 файла, общая библиотека, которая совместно используется обоими исполняемыми файлами, в которой хранятся интерфейсы и реализации прокси-сервера, хостинг-приложения и вашего клиентского приложения. Хостинговое приложение и общая библиотека потенциально могут быть объединены в одну сборку.
LibraryData.dll
//ISapProxy.cs
using System.Collections.Generic;
using System.ServiceModel;
namespace LibraryData
{
[ServiceContract]
public interface ISapProxy
{
[OperationContract]
List<SapData> QueryData(string query);
[OperationContract]
void Close();
}
}
//SapProxy.cs
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace LibraryData
{
public class SapProxy : ISapProxy
{
public List<SapData> QueryData(string query)
{
Thread.Sleep(new TimeSpan(0, 0, 5)); //represents time + cpu intensive method
return new List<SapData>();
}
public void Close()
{
Application.Exit();
}
}
}
//SapData.cs
using System.Runtime.Serialization;
namespace LibraryData
{
[DataContract]
public class SapData
{
}
}
HostApp.exe
//Program.cs
using LibraryData;
using System;
using System.ServiceModel;
using System.Windows.Forms;
namespace HostApp
{
class Program
{
[STAThread]
static void Main(string[] args)
{
System.Diagnostics.Debugger.Launch();
if (args.Length > 0)
{
var uri = new Uri("net.pipe://localhost");
using (var host = new ServiceHost(typeof(SapProxy), uri))
{
//If a client connection fails, shutdown.
host.Faulted += (obj, arg) => Application.Exit();
host.AddServiceEndpoint(typeof(ISapProxy), new NetNamedPipeBinding(), args[0]);
host.Open();
Console.WriteLine("Service has started and is ready to use.");
//Start a message loop in the event the service proxy needs one.
Application.Run();
host.Close();
}
}
}
}
}
YourProgram.exe
using LibraryData;
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
namespace SandboxConsole
{
class Program
{
static void Main(string[] args)
{
var connectionName = Guid.NewGuid().ToString();
ProcessStartInfo info = new ProcessStartInfo("HostApp", connectionName);
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
var proxyApp = Process.Start(info);
//Blocks till "Service has started and is ready to use." is printed.
proxyApp.StandardOutput.ReadLine();
var sapProxyFactory = new ChannelFactory<ISapProxy>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + connectionName));
Task.Factory.StartNew(() =>
{
var sapProxy = sapProxyFactory.CreateChannel();
try
{
var result = sapProxy.QueryData("Some query");
//Do somthing with the result;
}
finally
{
sapProxy.Close();
}
});
Console.WriteLine("ready");
//If you hit enter here before the 5 second pause in the library is done it will kill the hosting process forcefully "canceling" the operation.
Console.ReadLine();
proxyApp.Kill();
Console.ReadLine();
}
}
}
Одна ошибка, которую я не смог полностью устранить, это то, что если вы "Fail Fast" клиентское приложение (например, нажав на значок остановки в Visual Studio), у него никогда не будет возможности отключить приложение хостинга.
То, что вы хотите использовать, - это токен отмены, который позволит вам отменить вашу задачу, не прибегая к прямоте в явном виде.
Таким образом, вы измените свой вызов StartNew
как это:
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
var task = Task.Factory.StartNew<>(() => //your logic
, ts.Token);
Затем, если вам нужно отменить, вы просто делаете это:
tokenSource2.Cancel();
try
{
task.Wait();
}
catch(AggregateException aex)
{
//handle TaskCanceledException here
}
Как упоминалось в комментариях, если вы не находитесь в цикле (который, учитывая ваше описание задачи, интенсивно использующей ЦП, вероятно, маловероятно), у вас будут проблемы с очисткой после выполнения задачи SAP, но, скорее всего, не хуже, чем ваша текущая реализация.
Вот хорошая ссылка MSDN на отмену задачи.
РЕДАКТИРОВАТЬ
Спасибо Servy за то, что он указал, что этот подход не будет работать для сценария OP, так как он имеет дело с единственным долгосрочным методом и не может проверить состояние токена. Я оставлю это, но снова, это не будет работать для OP.