Search code examples
c#.netasynchronousdlldynamic-invoke

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


I begin by loading some dlls in a folder and storing a instance of the Core class together with the name as keys in a Dictionary<string, object>, like so:

   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}");
            }
        }
    }

some more methods for event handling, at the moment just dummies:

    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);
    }

later, I'm invoking the method Download with its respective parameters:

    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 });

    }

which is contained in the 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");
        }
    });

up to here everything works just fine...

    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();

the 'strange' thing is, this only happens when I try to run the method async, single threaded works just fine.

Also I've already checked multiple times, the token I pass through to the task is not canceled, apart from that, the token isn't even used in the task constructor.

I suspect it has something to do with the dynamic loading and invoking of the dlls.


Solution

  • Ok, after literally searching the .net source documentation for hours, since noone seemed to have the same problem, i finally found out what the root of all evil was ... who would have thought; my own stupidity: this is how my unit test looked like:

        [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 });
    
    
        }
    

    this obviously allows the test to just run through and cancel the spawned thread upon finishing ....

    this is one possible way it actually works, nothing to do with the actual invoking and so on...:

        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();
        }
    

    the actual code works just fine, just the unit test was set up wrong...