Медленная работа и ловля исключений в многопоточной программе - C#
Формулировка задачи:
Здравствуйте. Сразу хочу предупредить, что это мой первый проект поэтому судите строго, но не тролльте )
В общем после прочтения нескольких книг, естественно не до конца. Захотелось по быстрее написать что-то рабочее. И недолго думая решил написать многопоточный файл менеджер.
И практически сразу наткнулся на две проблемы.
1-ая: Как поймать исключение дочерних потоков в основном потоке ?
2-ая: Почему загрузка файла и сохранение его на диск происходит довольно длительное время?
Код либы в которой я качаю и сохраняю файл:
Весь проект целиком
using System;
using System.Net;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.Threading;
using System.Net.Mime;
using System.IO;
namespace phttp
{
public class HttpClient
{
protected readonly HttpConfigurator config;
protected readonly Uri uri;
protected Queue<HttpClientRange> queue;
protected List<HttpClientRange> ranges;
protected UInt32 threadsRun = 0;
protected UInt32 threadsFinished = 0;
protected string fileName;
protected string fullFileName;
protected string charSet;
protected long bytesRecieved = 0;
protected long bytesRecievedTotal = 0;
protected long contentLength;
static object locker = new object();
static object filelocker = new object();
public HttpClient(HttpConfigurator config)
{
this.config = config;
if (isValidURL(config.source)) this.uri = new Uri(config.source);
}
public void Run()
{
Thread t = new Thread(_Run);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
protected void _Run()
{
if (ranges != null)
{
ranges = null;
}
WriteLog("Get headers");
WebResponse headResponse = getHeadData(config.source, config.method);
UInt32 threadCount = 1;
if (headResponse.Headers.Get("Accept-Ranges") != "")
{
threadCount = config.threadCount;
}
WriteLog("Check path to file");
fileName = headResponse.ResponseUri.Segments[headResponse.ResponseUri.Segments.Length - 1].Trim();
var contentDispositionString = headResponse.Headers.Get("Content-Disposition");
if (contentDispositionString != null && contentDispositionString.Length > 0)
{
var contentDisposition = ContentDispositionHeaderValue.Parse(contentDispositionString);
if (contentDisposition.FileName.Length > 0)
{
fileName = contentDisposition.FileName;
}
}
var contentTypeString = headResponse.Headers.Get("Content-Type");
if (contentTypeString != null && contentTypeString.Length > 0)
{
var ContentType = new ContentType(contentTypeString);
if (ContentType.MediaType == "text/html")
{
fileName = Common.Fs.ChangeExtension(fileName, ".html");
}
charSet = ContentType.CharSet;
}
if (fileName == null || fileName.Length == 0) {
throw new HttpClientException(string.Format("Program cant get filename for {0}", config.source));
}
if (config.savePath == null || config.savePath.Length > 0)
{
fullFileName = Common.Fs.CreateFullFileName(config.savePath, fileName);
}
contentLength = Convert.ToUInt32(headResponse.Headers.Get("Content-Length"));
var contentRangeString = headResponse.Headers.Get("Content-Range");
if (contentRangeString != null && contentRangeString != "")
{
var crhv = ContentRangeHeaderValue.Parse(contentRangeString);
contentLength = (long) crhv.Length;
}
// make segmentation
WriteLog("Make segmentation download data");
UInt32 iterSegment = 0;
queue = new Queue<HttpClientRange>();
if (threadCount == 1)
{
var hcr = new HttpClientRange();
hcr.id = 1;
hcr.start = hcr.stop = hcr.bytes = 0;
queue.Enqueue(hcr);
}
else
{
long start = 0;
long stop = contentLength;
do
{
iterSegment++;
if (start > 0) start += 1;
var htr = new HttpClientRange();
htr.start = start;
htr.stop = start + config.segmentBytes;
if (htr.stop > stop) htr.stop = stop;
start = htr.stop;
htr.bytes = config.segmentBytes;
htr.id = iterSegment;
queue.Enqueue(htr);
} while (start < stop);
}
ranges = new List<HttpClientRange>();
do
{
if (threadsRun >= threadCount)
{
Thread.Sleep(250);
continue;
}
HttpClientRange segment = queue.Dequeue();
Thread t = new Thread(new ParameterizedThreadStart(Download));
t.SetApartmentState(ApartmentState.STA);
segment.thread = t;
segment.setState(HttpClientRange.STATE_START);
ranges.Add(segment);
t.Start(segment);
lock (locker)
{
threadsRun++;
}
} while (queue.Count > 0);
}
protected void Download(object o)
{
HttpClientRange hcr = o as HttpClientRange;
WriteLog(string.Format("Start thread [{0}]", hcr.id));
hcr.setState(HttpClientRange.STATE_START);
HttpWebRequest wr = HttpWebRequest.CreateHttp(uri);
if (hcr.stop > 0)
{
wr.AddRange((int) hcr.start, (int) hcr.stop);
}
wr.Method = config.method;
WebResponse response = wr.GetResponse();
Stream ReceiveStream = response.GetResponseStream();
byte[] buffer = new byte[1024];
int bytesRead;
long pos = hcr.start;
while ((bytesRead = ReceiveStream.Read(buffer, 0, buffer.Length)) != 0)
{
write(pos, buffer, bytesRead);
pos += bytesRead;
}
response.Close();
WriteLog(string.Format("Stop thread [{0}]", hcr.id));
hcr.setState(HttpClientRange.STATE_FINISH);
lock (locker)
{
threadsRun--;
}
}
public static bool isValidURL(string source)
{
Uri uriResult;
return Uri.TryCreate(source, UriKind.Absolute, out uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
public WebResponse getHeadData(string source, string method)
{
if (!isValidURL(source)) throw new HttpClientException("URL is incorrect");
HttpWebRequest wr = HttpWebRequest.CreateHttp(source);
wr.Method = method;
wr.AddRange(0,1);
var wres = wr.GetResponse();
wr.Abort();
wres.Close();
return wres;
}
public void WriteLog(string message)
{
if (config.logManager != null)
{
config.logManager.AddLogText(message);
}
}
protected void write(long pos, byte[] buffer, int bytesRead)
{
lock (filelocker)
{
FileStream outFile = new FileStream(fullFileName, FileMode.OpenOrCreate);
outFile.Seek(pos, SeekOrigin.Begin);
outFile.Write(buffer, 0, bytesRead);
outFile.Close();
}
lock (locker)
{
bytesRecieved += bytesRead;
}
Progress();
}
protected void Progress()
{
if (config.progress != null)
{
config.progress((int)(100*bytesRecieved /contentLength));
}
}
}
public class HttpClientRange
{
public static readonly string STATE_NEW = "new";
public static readonly string STATE_START = "start";
public static readonly string STATE_SUSPEND = "suspend";
public static readonly string STATE_FINISH = "finish";
public UInt32 id;
public long bytes;
public long start;
public long stop;
public readonly Guid guid;
public Thread thread;
public WebClient wc;
protected string _state;
public string state
{
get { return _state; }
}
public HttpClientRange()
{
_state = STATE_NEW;
guid = Guid.NewGuid();
}
public void setState(string state)
{
List<string> list = new List<string>();
list.Add(STATE_NEW);
list.Add(STATE_START);
list.Add(STATE_SUSPEND);
list.Add(STATE_FINISH);
foreach (var item in list)
{
if (item == state)
{
_state = state;
return;
}
}
throw new HttpClientRangeException(string.Format("State {0} is incorrect", state));
}
}
public class HttpClientException : Exception
{
public HttpClientException(string message) : base(message)
{
}
}
public class HttpClientRangeException : Exception
{
public HttpClientRangeException(string message) : base(message) { }
}
}using System;
using System.Collections.Generic;
using Loger;
namespace phttp
{
public delegate void Progress(int step);
public class HttpConfigurator
{
public readonly string source;
public readonly string savePath;
public readonly UInt32 threadCount;
public readonly long segmentBytes;
public bool writeToFile = true;
public string charSet;
public LogManager logManager;
public Progress progress;
private Dictionary<string, string> methods;
private string _method = "GET";
public string method
{
set { if (methods.ContainsKey(value)) _method = value; }
get { return _method; }
}
public HttpConfigurator(string source, string savePath, UInt32 threadCount, long segmentBytes)
{
this.source = source;
this.savePath = savePath;
this.threadCount = threadCount;
this.segmentBytes = segmentBytes;
methods = new Dictionary<string, string>();
methods.Add("GET", "GET");
methods.Add("POST", "POST");
}
}
}Решение задачи: «Медленная работа и ловля исключений в многопоточной программе»
textual
Листинг программы
public event ErrorEventHandler ErrorAppeared;
public void Run()
{
Thread t = new Thread(_Run);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
protected void _Run()
{
try
{
__Run();
}
catch (Exception ex)
{
if (ErrorAppeared != null)
ErrorAppeared(null, new ErrorEventArgs(ex));
}
}
protected void __Run()
{
if (ranges != null)
{
ranges = null;
}
.....