Обновление графического интерфейса с помощью задач

1

У меня есть странная проблема, которую я не могу решить, у меня есть форма, которую я открываю в другой форме, как только я открываю эту форму, и поскольку после завершения загрузки страницы нет события для запуска, на этапе загрузки события я таймер, который начнется через 5 секунд. Таймер вызовет задачу, которая будет загружать файлы, загрузка файлов обновляется в панели прогресса. Проблема в том, что все, что я пытаюсь изменить во время выполнения задач, не обновляет графический интерфейс и будет изменять графический интерфейс только после завершения всех задач, обратите внимание, что индикатор прогресса обновляется отлично. Вот мой код:

private void frm_HosterDownloader_Load(object sender, EventArgs e)
{
    StartDownloadTimer = new Timer();
    StartDownloadTimer.Tick += StartDownloadTimer_Tick;
    StartDownloadTimer.Interval = 5000;
    StartDownloadTimer.Start();
}

void StartDownloadTimer_Tick(object sender, EventArgs e)
{
    StartDownload();
    StartDownloadTimer.Stop();
}

private void StartDownload()
{
    int counter = 0;
    Dictionary<string, string> hosters = Hosters.GetAllHostersUrls();

    progressBar_Download.Maximum = hosters.Count * 100;
    progressBar_Download.Minimum = 0;
    progressBar_Download.Value = 0;

    foreach (KeyValuePair<string, string> host in hosters)
    {
        //Updating these tow lables never works, only when  everything finishes
        lbl_FileName.Text = host.Key;
        lbl_NumberOfDownloads.Text = (++counter).ToString() + "/" + hosters.Count().ToString();

        Task downloadTask = new Task(() =>
        {
            Downloader downloader = new Downloader(host.Value, string.Format(HostersImagesFolder + @"\{0}.png", IllegalChars(host.Key)));
            downloader.HosterName = host.Key;
            downloader.DownloadFinished += downloader_DownloadFinished;
            downloader.Execute();

        });
        downloadTask.Start();
        downloadTask.Wait();
    }
}

void downloader_DownloadFinished(object sender, ProgressEventArgs e)
{
    progressBar_Download.Value = progressBar_Download.Value + (int)e.ProgressPercentage;
}

Я устал помещать записи метки ярлыков в Задачу и даже пытался передать их как аргумент, который нужно обновить в событии DownloadFinish, но не повезло.

Редактировать:

Вот класс Downloader:

 public class Downloader : DownloaderBase
{
    public string HosterName { set; get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="Downloader"/> class.
    /// </summary>
    /// <param name="hoster">The hoster to download.</param>
    /// <param name="savePath">The path to save the video.</param>
    /// <param name="bytesToDownload">An optional value to limit the number of bytes to download.</param>
    /// <exception cref="ArgumentNullException"><paramref name="video"/> or <paramref name="savePath"/> is <c>null</c>.</exception>
    public Downloader(string hosterUrl, string savePath, int? bytesToDownload = null)
        : base(hosterUrl, savePath, bytesToDownload)
    { }

    /// <summary>
    /// Occurs when the downlaod progress of the file file has changed.
    /// </summary>
    public event EventHandler<ProgressEventArgs> DownloadProgressChanged;

    /// <summary>
    /// Starts download.
    /// </summary>
    /// <exception cref="IOException">The video file could not be saved.</exception>
    /// <exception cref="WebException">An error occured while downloading the video.</exception>
    public override void Execute()
    {
        this.OnDownloadStarted(new ProgressEventArgs(0, HosterName));

        var request = (HttpWebRequest)WebRequest.Create(this.HosterUrl);

        if (this.BytesToDownload.HasValue)
        {
            request.AddRange(0, this.BytesToDownload.Value - 1);
        }

        try
        {
            // the following code is alternative, you may implement the function after your needs
            request.Timeout = 100000;
            request.ReadWriteTimeout = 100000;
            request.ContinueTimeout = 100000;
            using (WebResponse response = request.GetResponse())
            {
                using (Stream source = response.GetResponseStream())
                {
                    using (FileStream target = File.Open(this.SavePath, FileMode.Create, FileAccess.Write))
                    {
                        var buffer = new byte[1024];
                        bool cancel = false;
                        int bytes;
                        int copiedBytes = 0;

                        while (!cancel && (bytes = source.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            target.Write(buffer, 0, bytes);

                            copiedBytes += bytes;

                            var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100, HosterName);

                            if (this.DownloadProgressChanged != null)
                            {
                                this.DownloadProgressChanged(this, eventArgs);

                                if (eventArgs.Cancel)
                                {
                                    cancel = true;
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (WebException ex)
        {
            if (ex.Status == WebExceptionStatus.Timeout)
                Execute();
        }

        this.OnDownloadFinished(new ProgressEventArgs(100, HosterName));
    }
}

 public abstract class DownloaderBase
{
    /// <summary>
    /// Initializes a new instance of the <see cref="DownloaderBase"/> class.
    /// </summary>
    /// <param name="hosterUrl">The video to download/convert.</param>
    /// <param name="savePath">The path to save the video/audio.</param>
    /// /// <param name="bytesToDownload">An optional value to limit the number of bytes to download.</param>
    /// <exception cref="ArgumentNullException"><paramref name="hosterUrl"/> or <paramref name="savePath"/> is <c>null</c>.</exception>
    protected DownloaderBase(string hosterUrl, string savePath, int? bytesToDownload = null)
    {
        if (hosterUrl == null)
            throw new ArgumentNullException("video");

        if (savePath == null)
            throw new ArgumentNullException("savePath");

        this.HosterUrl = hosterUrl;
        this.SavePath = savePath;
        this.BytesToDownload = bytesToDownload;
    }

    /// <summary>
    /// Occurs when the download finished.
    /// </summary>
    public event EventHandler<ProgressEventArgs> DownloadFinished;

    /// <summary>
    /// Occurs when the download is starts.
    /// </summary>
    public event EventHandler<ProgressEventArgs> DownloadStarted;

    /// <summary>
    /// Gets the number of bytes to download. <c>null</c>, if everything is downloaded.
    /// </summary>
    public string HosterUrl { get; set; }

    /// <summary>
    /// Gets the number of bytes to download. <c>null</c>, if everything is downloaded.
    /// </summary>
    public int? BytesToDownload { get; private set; }

    /// <summary>
    /// Gets the path to save the video/audio.
    /// </summary>
    public string SavePath { get; private set; }

    /// <summary>
    /// Starts the work of the <see cref="DownloaderBase"/>.
    /// </summary>
    public abstract void Execute();

    protected void OnDownloadFinished(ProgressEventArgs e)
    {
        if (this.DownloadFinished != null)
        {
            this.DownloadFinished(this, e);
        }
    }

    protected void OnDownloadStarted(ProgressEventArgs e)
    {
        if (this.DownloadStarted != null)
        {
            this.DownloadStarted(this, e);
        }
    }
}
  • 0
    И есть Shown событие.
  • 0
    использование показанного события не работает для меня, потому что некоторые элементы управления не показывают
Показать ещё 5 комментариев
Теги:
multithreading
winforms
task-parallel-library

2 ответа

3
Лучший ответ

Нет смысла использовать задачу таким образом:

downloadTask.Start();
downloadTask.Wait();

Wait() блокирует вызывающий код и обрабатывает событие. Ваши загрузки эффективно выполняются в основном потоке графического интерфейса, блокируя его.

Решение

//downloadTask.Wait();

Кажется, вам это не нужно.

  • 0
    не Wait () заблокирует только задачу. Операторы, которые обновляют элементы управления, находятся внутри foreach, а не в теле задачи. Плюс я уже попробовал то, что вы предложили, но тот же результат.
  • 1
    Нет, он блокирует поток, который вызывает Wait ().
Показать ещё 4 комментария
2

Существует редко хорошая причина использовать потоки (или новые, которые вы создаете, или threadpool) для работы с IO-связью. Ниже приведена async альтернатива вашему синхронному методу Execute:

public async Task ExecuteAsync()
{
    this.OnDownloadStarted(new ProgressEventArgs(0, HosterName));

    var httpClient = new HttpClient();
    var request = (HttpWebRequest)WebRequest.Create(this.HosterUrl);

    if (this.BytesToDownload.HasValue)
    {
        request.AddRange(0, this.BytesToDownload.Value - 1);
    }

    try
    {
        request.Timeout = 100000;
        request.ReadWriteTimeout = 100000;
        request.ContinueTimeout = 100000;

        var response = await httpClient.SendAsync(request);
        var responseStream = await response.Content.ReadAsStreamAsync();

        using (FileStream target = File.Open(this.SavePath, FileMode.Create, FileAccess.Write))
        {
            var buffer = new byte[1024];
            bool cancel = false;
            int bytes;
            int copiedBytes = 0;

            while (!cancel && (bytes = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await target.WriteAsync(buffer, 0, bytes);

                copiedBytes += bytes;

                var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100, HosterName);

                if (this.DownloadProgressChanged != null)
                {
                    this.DownloadProgressChanged(this, eventArgs);

                    if (eventArgs.Cancel)
                    {
                        cancel = true;
                    }
                }
            }
        }
    }

    catch (WebException ex)
    {
        if (ex.Status == WebExceptionStatus.Timeout)
    }

    this.OnDownloadFinished(new ProgressEventArgs(100, HosterName));
}

Теперь нет необходимости использовать Task.Wait или создать новую Task. Работа с привязкой IO является асинхронной по своей природе. В сочетании с новыми ключевыми словами async-await в С# 5 вы можете поддерживать свой пользовательский интерфейс в течение всего времени, так как каждый из await возвращает управление вызывающему методу и освобождает ваш насос сообщений winforms, чтобы обработать больше сообщений в то время.

private async void frm_HosterDownloader_Load(object sender, EventArgs e)
{
    await Task.Delay(5000);
    await StartDownloadAsync();
}

private async Task StartDownloadAsync()
{
    int counter = 0;
    Dictionary<string, string> hosters = Hosters.GetAllHostersUrls();

    progressBar_Download.Maximum = hosters.Count * 100;
    progressBar_Download.Minimum = 0;
    progressBar_Download.Value = 0;

    var downloadTasks = hosters.Select(hoster => 
    {
        lbl_FileName.Text = hoster.Key;
        lbl_NumberOfDownloads.Text = (++counter).ToString() + "/" + hosters.Count().ToString();

        Downloader downloader = new Downloader(host.Value, string.Format(HostersImagesFolder + @"\{0}.png", IllegalChars(host.Key)));
        downloader.HosterName = host.Key;
        downloader.DownloadFinished += downloader_DownloadFinished;

        return downloader.ExecuteAsync(); 
    });

    return Task.WhenAll(downloadTasks);
}

Обратите внимание, что я изменил ваш таймер на Task.Delay, так как он использует таймер и использует его только один раз.

Если вы хотите больше использовать async-await, вы можете начать здесь.

  • 0
    Код OP на самом деле никогда не спал с потоком пула потоков, он просто использовал асинхронность на основе событий вместо асинхронности на основе задач. Другой стиль, но одинаково действителен.
  • 1
    Task downloadTask = new Task , downloadTask.Start() . Он использовал нить пула потоков. Он не спал это, но он определенно потреблял это.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню