正如您在代码中间看到的那样,有一个丑陋的线程阻塞代码,它等待文件下载并在命令行中启用进度报告。我的意思是,这仍然比Thread.Sleep()
或忙于等待要好,对吧?无论如何,我确实知道Wait/Pulse
,但我不知道如何在这里应用它。
完全重构我的代码以更好地适应单个异步操作是最干净的解决方案吗?是否可以覆盖WebClient
类中的某些内容以利用Wait/Pulse
类型的等待?
项目和有问题的函数:Github
相关片段:
static void GetPatch(KeyValuePair<string, string> entry, string part)
{
string url = entry.Key,
fname = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1),
path = G.patchPath + "\" + fname;
bool exists = File.Exists(path);
Console.Write(fname + " ... ");
string message = "local";
if ((exists && GetSHA1(path) != entry.Value) || !exists)
{
if (exists) File.Delete(path);
G.wc.DownloadProgressChanged += Wc_DownloadProgressChanged;
G.wc.DownloadFileAsync(new Uri(url), part);
while (G.wc.IsBusy)
{
// There must be a better way
new System.Threading.ManualResetEvent(false).WaitOne(200);
}
G.wc.DownloadProgressChanged -= Wc_DownloadProgressChanged;
message = "done";
}
if (File.Exists(part)) File.Move(part, path);
G.patchFNames.Enqueue(fname);
Green(message);
}
private static void Wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
int p = e.ProgressPercentage;
p = p < 100 ? p : 99;
Console.Write("{0:00}%bbb", p);
}
请耐心等待,这是我用 C# 编写的第一个项目,我是 OOP 和 C# 的绝对新手。
注释中的 url 复制代码:
public void DownloadFile(Uri uri, string desintaion)
{
using(var wc = new WebClient())
{
wc.DownloadProgressChanged += HandleDownloadProgress;
wc.DownloadFileCOmpleted += HandleDownloadComplete;
var syncObj = new Object();
lock(syncObject)
{
wc.DownloadFileAsync(sourceUri, destination, syncObject);
//This would block the thread until download completes
Monitor.Wait(syncObject);
}
}
//Do more stuff after download was complete
}
public void HandleDownloadComplete(object sender, AsyncCompletedEventArgs args)
{
lock(e.UserState)
{
//releases blocked thread
Monitor.Pulse(e.UserState);
}
}
public void HandleDownloadProgress(object sender, DownloadProgressChangedEventArgs args)
{
//Process progress updates here
}
您的高级代码应如下所示
public async Task StartFileDownload()
{
var downloadTask = StartDownload();
var monitoringTask = StartMonitoringProgress();
await Task.WhenAll(downloadTask, monitoringTask);
}
您的监控任务应该是每 N 毫秒检查一次下载进度并更新进度条。虽然由于您不在 UI 进程中,因此无法直接执行此操作,但您必须"调度"UI 更新。
关于上述解决方案的说明:
如果异步下载未启动(例如,如果您在 WPF 应用程序内部(,则可能需要将线程同步上下文设置为使用线程池。如果执行此操作,则需要确保不更改回调中的任何 UI 元素。
SynchronizationContext orig = SynchronizationContext.Current;
// Use thread pool and not the SyncContext for WPF
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
wc.DownloadDataAsync(new Uri(url), syncObject);
SynchronizationContext.SetSynchronizationContext(orig);