使用 Task.Factory.StartNew 动态调用方法时,新 CookieContainer 上的线程被取消

Thread was canceled on new CookieContainer when dynamically Invoking Method with Task.Factory.StartNew

我首先在文件夹中加载一些 dll,然后将 Core class 的实例连同名称作为键存储在 Dictionary<string, object> 中,如下所示:

   const string SEARCH_PATH = "./Cores";
   const string FILE_NAME_MASK = "*.core.dll";

   private IProgress<double> _progress;

   private Dictionary<string, object> _dlls;

   public void LoadCores() {
        //consts
        string[] BindMethods = { "PartFinished", "DownloadFinished", "ReportError" };
        //get dlls to load
        IEnumerable<string> dlls = Directory.EnumerateFiles(SEARCH_PATH, FILE_NAME_MASK);
        //amount of dlls
        int i = 0;

        Assembly assembly;
        Type coreType;
        object instance;

        EventInfo eInfo;
        MethodInfo mInfo;
        Delegate del;
        string asmName;
        Type instanceType;
        //try loading all core.dll's
        foreach (string fileName in dlls) {
            Debug.Write("Loading ");
            Debug.WriteLine(fileName);
            try {
                //load assembly
                assembly = Assembly.LoadFile(Path.GetFullPath(fileName));
                //find core type
                asmName = assembly.GetName().Name;
                coreType = assembly.GetType(asmName + ".Core", true);
                //create instance of core
                instance = Activator.CreateInstance(coreType, _progress);
                //bind core events
                foreach (string t in BindMethods) {
                    instanceType = instance.GetType();
                    if (instanceType.GetMethod("Download") == null) {
                        throw new MissingMethodException($"The Core in '{fileName}' does not contain a Method like 'Downlaod(IEnumrable<Uri> uris, string pathTemplate)'", "Download");
                    }
                    eInfo = instanceType.GetEvent(t);
                    mInfo = eInfo.GetAddMethod();
                    try {
                        del = Delegate.CreateDelegate(eInfo.EventHandlerType, this, $"On{t}");
                    } catch (Exception ex) {
                        throw new ArgumentException(
                            $"the function '{t}' requires a Method called 'On{t}' in this scope with an '{eInfo.EventHandlerType}' compatibility",
                            ex);
                    }
                    mInfo.Invoke(instance, new object[] { del });
                }
                //dll loaded successfull
                _dlls.Add(asmName.Split('.')[0], instance);
                i++;
            } catch (Exception ex) {
                OnReportError(this, $"Error Loading {fileName}:\n{ex}");
            }
        }
    }

一些更多的事件处理方法,atm 只是傻瓜:

    public void OnPartFinished(object sender, KeyValuePair<int, Exception> data) {
        Debug.WriteLine("Part finished: " + data.Key + "\n" + data.Value);
    }
    public void OnDownloadFinished(object sender, EventArgs args) {
        Debug.WriteLine("Download finished.");
    }
    public void OnReportError(object sender, string data) {
        Debug.WriteLine("Error Reported: " + data);
    }

稍后,我将使用其各自的参数调用方法 Download

    void Test(string method, IEnumerable<Uri> links, string pathTemplate) {
        object instance = _dlls[method];
        MethodInfo mInfo = instance.GetType().GetMethod("Download");

        mInfo.Invoke(instance, new object[] { links, pathTemplate });

    }

dll 中包含:

public class Core {
    private CancellationTokenSource _cancelToken;

    private readonly IProgress<double> _progress;

    public event EventHandler<KeyValuePair<int, Exception>> PartFinished;
    public event EventHandler DownloadFinished;
    public event EventHandler<string> ReportError;

    public Core(IProgress<double> progressReporter) {
        _progress = progressReporter;
    }

    public async void Download(IEnumerable<Uri> uris, string pathTemplate) {
        _cancelToken = new CancellationTokenSource();
        _progress.Report(0);

        int i = 1;
        string p = string.Empty;
        foreach (Uri uri in uris) {
            try {

                if (File.Exists(p = string.Format(pathTemplate, i++)))
                    OnPartFinished(i - 1, new IOException("File already exists"));
                else {
                    using (Stream stream = new FileStream(p, FileMode.Create)) {
                        await DownloadMegaUpCore(uri, stream, _cancelToken.Token);
                        //DownloadMegaUpSyncronized(uri, stream, _cancelToken.Token);
                    }
                    OnPartFinished(i - 1, null);
                }
            } catch (OperationCanceledException ex) {
                //delete semi-corrupt / unfinished files
                OnReportError($"canceled on part {i - 1}");
                File.Delete(p);
                OnPartFinished(i - 1, ex);
                break;
            } catch (Exception ex) {
                OnReportError($"error on part part {i - 1}:\n{ex}\n\nSkipping...");
                OnPartFinished(i - 1, ex);
            }
        }
        OnDownloadFinished();
    }

    private Task DownloadMegaUpCore(Uri url, Stream stream, CancellationToken token) => Task.Factory.StartNew(() => {
        try {
            DownloadMegaUpSyncronized(url, stream, token);
        } catch (Exception ex) {
            if (File.Exists("./part1.rar"))
                File.Delete("./part1.rar");
        }
    });

至此一切正常......

    void DownloadMegaUpSyncronized(Uri url, Stream stream, CancellationToken token) {
        int TextDlBlockSize = 1024 * 2;
        int DataDlBlockSize = 1024 * 64;

        //---------------- todo: move constants
        HttpWebRequest request;
        HttpWebResponse response = null;
        CookieContainer cContainer = null;

        cContainer = new CookieContainer(); // < here the exception accures

        request = (HttpWebRequest)WebRequest.Create(url);
        request.Timeout = 5000;
        response = (HttpWebResponse)request.GetResponse();

'strange' 事情是,只有当我尝试 运行 异步方法时才会发生这种情况,单线程工作得很好。

而且我已经检查了很多次,我传递给任务的令牌没有被取消,除此之外,任务构造函数中甚至没有使用令牌。

我怀疑是dll的动态加载和调用有关

好的,在 .net 源文档中搜索了几个小时之后,由于似乎没有人遇到同样的问题,我终于找到了万恶之源......谁会想到;我自己的愚蠢: 这是我的单元测试的样子:

    [TestMethod]
    public void DllLoadingTest() {
        Init();
        LoadCores();
        TestAsync(method,
            new[] { new Uri(lnk) },
            "./part{0}.rar");
    }

    void TestAsync(string method, IEnumerable<Uri> links, string pathTemplate) {
        object instance = _dlls[method];
        MethodInfo mInfo = instance.GetType().GetMethod("Download");
        mInfo.Invoke(instance, new object[] { links, pathTemplate });


    }

这显然允许测试 运行 通过并在完成后取消生成的线程....

这是它实际工作的一种可能方式,与实际调用等无关...:[=​​13=]

    private AutoResetEvent _are;
    [TestMethod]
    public void DllLoadingTest() {
        Init();
        LoadCores();
        TestAsync(method,
            new[] { new Uri(lnk) },
            "./part{0}.rar");
    }

    void TestAsync(string method, IEnumerable<Uri> links, string pathTemplate) {
        object instance = _dlls[method];
        MethodInfo mInfo = instance.GetType().GetMethod("Download");
        mInfo.Invoke(instance, new object[] { links, pathTemplate });
        _are.WaitOne();

    }

[...]

    public void OnDownloadFinished(object sender, EventArgs args) {
        Debug.WriteLine("Download finished.");
        _are.Set();
    }

实际代码工作正常,只是单元测试设置错误...