停止/销毁/取消一个 C# 任务

3
我正在尝试通过从Google Drive / Dropbox / FTP等各种存储库导入DLL来部署Windows服务中的DLL。
但是,在实例化任何新DLL之前,我希望先关闭以前正在运行的实例。
在这个过程中,我使用了任务和反射。
我无法弄清楚如何取消任务,该任务在运行时实例化DLL(因为实例化的dll是一个长时间运行的应用程序示例文件监视器)。
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken ct = cts.Token;

            // instantiate the dll though reflection
        t = Task.Factory.StartNew(() =>
        {
            try
            {
                Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + dllName);
                Type type = assembly.GetType("myclass.Program");

                MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
                          BindingFlags.Static | BindingFlags.Instance);
                minfo.Invoke(Activator.CreateInstance(type), null);

            }
            catch (Exception ex)
            {

                log.Error(ex.ToString());
            }
        }, cts.Token);

问题 : 在我的应用程序检测到一个新的dll并尝试通过这个任务代码执行它之前,我想要取消任务t。

编辑 我已经删除了取消令牌代码,因为它会出现故障。以下是带有取消令牌的实际代码。

    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken ct = cts.Token;

if (t != null)
        {
            cts.Cancel();
            try
            {
                ct.ThrowIfCancellationRequested();
            }
            catch (Exception ex)
            {

                cts.Dispose();
                t.Dispose();
            }

        }

                // instantiate the dll though reflection
        t = Task.Factory.StartNew(() =>
        {
            try
            {
                Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + dllName);
                Type type = assembly.GetType("myclass.Program");

                MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
                          BindingFlags.Static | BindingFlags.Instance);
                minfo.Invoke(Activator.CreateInstance(type), null);

            }
            catch (Exception ex)
            {

                log.Error(ex.ToString());
            }
        }, cts.Token);

我的想法是,如果我可以取消和处理持有实例化上下文的任务,那么程序集就会被释放,然后我就能够通过该任务更新程序集并重新实例化它。

我知道我在某个地方做错了,请详细解释一下。

编辑

我对assemblyDomain.DoCallBack(delegate)寄予厚望,但是我遇到了一个错误。以下是引发错误的简化代码版本。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Reflection;


namespace AppDomain
{
    [Serializable]
    class Program
    {
        static System.AppDomain assemblyDomain = null;

        static void Main(string[] args)
        {

            var inp = "go";

            while (inp.ToString().ToLower().Trim() != "stop")
            {
                start();
                inp = Console.ReadLine();
            }

        }

        private static void start()
        {


            //Check if appdomain and assembly is already loaded
            if (assemblyDomain != null)
            {
                //unload appDomain and hence the assembly
                System.AppDomain.Unload(assemblyDomain);

                //Code to download new dll
            }

            string cwd = System.AppDomain.CurrentDomain.BaseDirectory;

            string sourceFileName = @"C:\Users\guest\Documents\visual studio 2010\Projects\DotNetTraining\Lecture 1 - dotNetProgramExecution\bin\Debug\Lecture 1 - dotNetProgramExecution.exe";

            string dllName = "Lecture 1 - dotNetProgramExecution.exe";

            // copy the file
            if (File.Exists(cwd + dllName))
            {
                File.Delete(cwd + dllName);
            }

            File.Copy(sourceFileName, cwd + dllName);

            assemblyDomain = System.AppDomain.CreateDomain("assembly1Domain", null);
            assemblyDomain.DoCallBack(() =>
               {
                   var t = Task.Factory.StartNew(() =>
                   {
                       try
                       {

                           string sss = "";
                           Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + dllName);
                           Type type = assembly.GetType("Lecture_1___dotNetProgramExecution.Program");

                           MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
                                     BindingFlags.Static | BindingFlags.Instance);
                           minfo.Invoke(Activator.CreateInstance(type), null);



                           //        //var pathToDll = @"assembly path";
                           //        //var dllName = "assembly name";
                           //        var assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + dllName);
                           //        var targetAssembly = assembly.CreateInstance("Lecture_1___dotNetProgramExecution.Program");
                           //        Type type = targetAssembly.GetType();
                           //        MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
                           //        minfo.Invoke(targetAssembly, null);

                       }
                       catch (Exception ex)
                       {

                           Console.WriteLine(ex.ToString());
                       }
                   });
               });
        }
    }
}


错误:
在程序集“AppDomain,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”中,类型“AppDomain.Program+<>c__DisplayClass2”未标记为可序列化。

堆栈跟踪:

at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
   at AppDomain.Program.start() in c:\users\guest\documents\visual studio 2010\Projects\DotNetTraining\AppDomain\Program.cs:line 58
   at AppDomain.Program.Main(String[] args) in c:\users\guest\documents\visual studio 2010\Projects\DotNetTraining\AppDomain\Program.cs:line 24
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

请注意:我已将我导入的程序集中的类Program标记为可序列化。
namespace Lecture_1___dotNetProgramExecution
{
    [Serializable]
    class Program
    {
        static void Main()
        {

更新:

动态拉取程序集的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;

namespace Lecture_1___dotNetProgramExecution
{
    [Serializable]
    class Program
    {
        static void Main()
        {
            try
            {

                Task.Factory.StartNew(() =>
                {

                    while (true)
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.Append("log something new yippe ");
                        // flush every 20 seconds as you do it
                        File.AppendAllText(@"C:\logs.txt", sb.ToString());
                        sb.Clear();
                        Thread.Sleep(3000);
                    }

                });



                FileSystemWatcher fsw = new FileSystemWatcher();

                fsw.Path = @"c:\watched";
                //fsw.filter = ".dll";
                fsw.Created += new FileSystemEventHandler(fsw_Created);
                fsw.BeginInit();
                //throw new FileNotFoundException();
                Console.ReadLine();

            }
            catch (Exception ex)
            {

                Task.Factory.StartNew(() =>
                {

                    while (true)
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.Append("loggind froom exception log something");
                        // flush every 20 seconds as you do it
                        File.AppendAllText(@"C:\logs.txt", sb.ToString());
                        sb.Clear();
                        Thread.Sleep(1000);
                    }

                });
                Console.ReadLine();
            }
        }

        static void fsw_Created(object sender, FileSystemEventArgs e)
        {
            throw new NotImplementedException();
        }


    }
}

哪一个操作速度较慢的是 Assembly.LoadFile - NeddySpaghetti
3
如果您想部署更新的程序集,我认为您需要在一个单独的“AppDomain”中加载您的程序集,以使它们能够被卸载。您可以关闭该“AppDomain”来卸载程序集,并创建一个新的“AppDomain”来加载新的程序集。 - Enigmativity
cts.cancel不起作用。 - ankur
@Enigmativity:我不想这样做。我想保持在我的应用程序上下文中,删除旧任务->然后删除旧程序集->接着将新程序集引入(通过下载/复制到我的本地bin目录)->再次触发任务代码,以便现在实例化新程序集并销毁旧上下文。 - ankur
这里的基本问题是minfo.Invoke()从未返回。因此,您的任务将永远运行下去。而且没有检查取消令牌,您调用的Main()方法对此一无所知。因此不知道何时停止。启动另一个线程来调用Main()解决了即时问题,但并没有解决真正的问题,您仍然有代码在运行,不知道何时需要停止。放弃您现有的内容并重新考虑您的方法,现在的做法不会让您走得更远。 - Hans Passant
显示剩余10条评论
1个回答

4
从你的问题中可以看出,如果有可用的升级,则希望卸载动态加载的程序集,然后重新加载最新的程序集。在这种情况下,取消操作是无效的。实际上,我没有看到你在任何地方使用取消令牌。
卸载动态加载的程序集的唯一方法是首先将程序集加载到单独的应用程序域中,然后在不再需要程序集时卸载应用程序域本身。因此,您应该按照以下步骤进行:
1.创建一个新的应用程序域。保留应用程序域的引用,稍后需要它来卸载域和程序集。
2.在新创建的应用程序域中加载程序集。
3.根据需要创建类型的实例,然后执行其方法。
4.当dll的新版本可用时,请卸载先前创建的应用程序域。这将自动卸载程序集。
5.下载新程序集并从步骤1开始。
请参见此处如何在其中加载/卸载应用程序域和程序集:在C#中使用AppDomain动态加载和卸载dll 编辑:以下是使用AppDomain.DoCallback的代码片段
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;


namespace AppDomain
{
[Serializable]
class Program
{
    static System.AppDomain assemblyDomain = null;

    static void Main(string[] args)
    {

        var inp = "go";

        while (inp.ToString().ToLower().Trim() != "stop")
        {
            start();
            inp = Console.ReadLine();
        }

    }

    private static void start()
    {


        //Check if appdomain and assembly is already loaded
        if (assemblyDomain != null)
        {
            //unload appDomain and hence the assembly
            System.AppDomain.Unload(assemblyDomain);

            //Code to download new dll
        }

        string cwd = System.AppDomain.CurrentDomain.BaseDirectory;

        string sourceFileName = @"C:\Users\deepak\Documents\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe";


        string dllName = "ConsoleApplication2.exe";

        // copy the file
        if (File.Exists(cwd + dllName))
        {
            File.Delete(cwd + dllName);
        }

        File.Copy(sourceFileName, cwd + dllName);

        assemblyDomain = System.AppDomain.CreateDomain("assembly1Domain", null);
        assemblyDomain.DoCallBack(() =>
        {
            var t = Task.Factory.StartNew(() =>
            {
                try
                {

                    string sss = "";
                    string dllName1 = "ConsoleApplication2.exe";
                    Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + dllName1);
                    Type type = assembly.GetType("Lecture_1___dotNetProgramExecution.Program");

                    MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
                              BindingFlags.Static | BindingFlags.Instance);
                    minfo.Invoke(Activator.CreateInstance(type), null);
                }
                catch (Exception ex)
                {

                    Console.WriteLine(ex.ToString());
                }
            });
        });
    }
}
}


using System;
using System.Text;
using System.Threading;

namespace Lecture_1___dotNetProgramExecution
{
[Serializable]
class Program
{
    static void Main()
    {
        while (true)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("log something new yippe ");
            // flush every 20 seconds as you do it
            //File.AppendAllText(@"C:\logs.txt", sb.ToString());
            Console.WriteLine(sb.ToString());
            sb.Clear();
            Thread.Sleep(3000);
        }
    }
}
}

你完全不需要取消令牌。一旦我能够访问我的笔记本电脑,我会在我的帖子里编辑代码。 - Deepak Bhatia
我在阅读了将程序集加载到单独的应用程序域的功能后,也做了完全相同的事情。但是几分钟后,我的上述代码发现有一个更新版本可用...然后再次触发上述代码 - > 我成功卸载了应用程序域 - > 然后当我尝试从当前 bin 中删除旧的 dll 时,它会抛出一个错误,指出该程序集被拒绝访问路径“mydll.exe”。 - ankur
我已经添加了被动态引入的dll的代码,请查看。 - ankur
我今晚会尝试这个并且回复你。 - Deepak Bhatia
不客气。像“AppDomain.Program+<>c__DisplayClass2'”这样的类名是由框架为闭包或匿名方法生成的。 - Deepak Bhatia
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接