使用 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();
}
实际代码工作正常,只是单元测试设置错误...
我首先在文件夹中加载一些 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();
}
实际代码工作正常,只是单元测试设置错误...