Thread.Abort的替代方案,用于cpu +时间密集型方法

时间:2014-01-28 14:52:28

标签: c# multithreading abort taskfactory thread-abort

我对multithreading applications有疑问。我使用TaskFactory来启动一个cpu +时间密集型方法。此方法是对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 个答案:

答案 0 :(得分:5)

最好的选择是查看该方法是否支持任何类型的cooperative cancelation

但是,如果不可能,则取消长时间运行进程的下一个最佳选择是使用运行长时间运行进程的第二个可执行文件,然后通过某种形式的IPC(WCF over Named Pipes工作)与第二个可执行文件进行通信非常适合机内IPC)“代理”所有呼叫。当您需要取消您的流程时,您可以终止第二个代理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();

        }
    }
}

我无法完全压制的一个错误是,如果您“快速失败”客户端应用程序(例如通过单击visual studio中的停止图标),它永远不会有机会告诉托管应用程序关闭。

答案 1 :(得分:-2)

您想要使用的是Cancellation Token,这样您就可以取消任务,而不必像现在这样明确地删除线程。

因此,您可以修改对此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
}

正如评论中所提到的那样,除非你处于循环中(考虑到你对CPU密集型任务的描述可能不太可能),否则在SAP任务之后你将无法清理,但很可能不会比你当前更糟糕实施

关于任务取消,这是一个很好的MSDN reference

修改
感谢Servy指出这种方法不适用于OP的场景,因为他正在处理一个长期运行的方法,并且无法检查令牌的状态。我会把它留下来,但是再一次,这对OP来说不起作用。