【问题标题】:Cancel FTP Download in BackgroundWorker C#在BackgroundWorker C#中取消FTP下载
【发布时间】:2015-04-17 15:08:46
【问题描述】:

我有以下方法从 FTP 服务器下载文件:

public bool DownloadFTP(string LocalDirectory, string RemoteFile,
                        string Login, string Password)
    {

        try
        {
            FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(RemoteFile);
            ftp.Credentials = new NetworkCredential(Login, Password);
            ftp.KeepAlive = false;
            ftp.Method = WebRequestMethods.Ftp.DownloadFile;
            Form1 form = new Form1();
            ftp.UseBinary = true;
            long pesoarchivo = obtenertamano(RemoteFile, Login, Password);

            ftp.Proxy = null;
            ftp.EnableSsl = false;

            LocalDirectory = Path.Combine(LocalDirectory, Path.GetFileName(RemoteFile));
            using (FileStream fs = new FileStream(LocalDirectory, FileMode.Create,
                                        FileAccess.Write, FileShare.None))
            using (Stream strm = ftp.GetResponse().GetResponseStream())
            {
                int buffLength = 2048;
                byte[] buff = new byte[buffLength];
                // Leer del buffer 2kb cada vez
                int contentLen=strm.Read(buff, 0, buffLength);
                int bytes = 0;

                try
                {

                    while (contentLen > 0)
                    {

                        fs.Write(buff, 0, contentLen);
                        contentLen = strm.Read(buff, 0, buffLength);

                        bytes += contentLen;
                        int totalSize = (int)(pesoarchivo) / 1000;

                        if (bytes != 0)
                        {
                            // Console.WriteLine(((bytes / 1000) * 100 / totalSize).ToString()+"     "+ totalSize.ToString());
                            backgroundWorker1.ReportProgress((bytes / 1000) * 100 / totalSize, totalSize);
                            //     label1.Text = ((bytes / 1000) * 100 / totalSize).ToString();
                        }
                        else { }



                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString())                }

            }

            return true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString())      
            return false;
        }

然后我有一个使用按钮调用的 BackgroundWorker。 在 BackgroundWorker "DoWork" 事件中,我调用了 DownloadFTP 方法:

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {

              if (descargaFTP(folder + "\\folder\\", ftpServer+ file, User, Password))
                        {
                           MessageBox.Show("Download Completed");
            }



  }

我想添加一个按钮来取消 FTPDownload。我该怎么做?

【问题讨论】:

    标签: c# ftp backgroundworker


    【解决方案1】:

    为此,首先摆脱BackgroundWorker 并使用FtpWebRequest 类的异步成员。据我所知,同步方法是不可取消的,但是可以使用FtpWebRequest.Abort()方法取消异步操作。

    例如:

    // This needs to be an instance field instead of local variable, so that
    // the object's `Abort()` method can be called elsewhere
    private FtpWebRequest ftp;
    
    public async Task DownloadFTPAsync(CancellationToken cancelToken,
       string LocalDirectory, string RemoteFile, string Login, string Password)
    {
    
        try
        {
            ftp = (FtpWebRequest)FtpWebRequest.Create(RemoteFile);
            ftp.Credentials = new NetworkCredential(Login, Password);
            ftp.KeepAlive = false;
            ftp.Method = WebRequestMethods.Ftp.DownloadFile;
            ftp.UseBinary = true;
    
            // [PD]: I don't know what this method does or even what the name
            // means, but on the assumption that because it needs the login
            // and password information, it may be doing some longer network
            // I/O or something, I'm wrapping it in a task to ensure the calling
            // thread remains responsive.
            long pesoarchivo = await Task.Run(() => obtenertamano(RemoteFile, Login, Password));
    
            // This is invariant, so might as well calculate it here, rather
            // than over and over again in the stream I/O loop below
            int totalSize = (int)(pesoarchivo) / 1000;
    
            ftp.Proxy = null;
            ftp.EnableSsl = false;
    
            LocalDirectory = Path.Combine(LocalDirectory, Path.GetFileName(RemoteFile));
            using (FileStream fs = new FileStream(LocalDirectory, FileMode.Create,
                                        FileAccess.Write, FileShare.None))
            using (Stream strm = (await ftp.GetResponseAsync()).GetResponseStream())
            {
                int buffLength = 2048;
                byte[] buff = new byte[buffLength];
                int contentLen;
    
                // Check for cancellation. This will throw an exception
                // if cancellation was requested; if you'd prefer, you can
                // check the cancelToken.IsCancellationRequested property
                // instead and just return, or whatever.
                cancelToken.ThrowIfCancellationRequested();
    
                while ((contentLen = await strm.ReadAsync(buff, 0, buff.Length)) > 0)
                {
                    await fs.WriteAsync(buff, 0, contentLen);
    
                    // Here, the calling thread (i.e. the UI thread) is executing
                    // the code so just update whatever progress indication
                    // you want directly.
                    label1.Text = ((fs.Position / 1000) * 100 / totalSize).ToString();
    
                    // Keep checking inside the loop
                    cancelToken.ThrowIfCancellationRequested();
                }
            }
        }
        finally
        {
            ftp = null;
        }
    }
    

    注意事项:

    • 我清理了一些处理实际 I/O 的循环。您拥有的代码并没有像应有的那样井井有条。
    • 实际上可以通过调用Stream.CopyToAsync() 来完全替换I/O 循环。但是,当然在这种情况下,您将无法进行进度更新。我假设进度更新是一个重要功能,所以我将代码保留在上面,即可以制作它们。
    • 因为async 方法可以将异常处理移到调用者中,所以我在此处将其删除并在此处合并。当然,如果您愿意,您可以采用任何一种方式。

    如上实现的下载代码,你可以这样调用它(例如):

    CancellationTokenSource _cancelTokenSource;
    
    async void downloadButton_Click(object sender, EventArgs e)
    {
        // User clicked the button to start download
    
        _cancelTokenSource = new CancellationTokenSource();
    
        try
        {
            // For example, update the UI button state so user can't start a
            // new download operation until this one is done, but they can cancel
            // this one.
            downloadButton.Enabled = false;
            cancelButton.Enabled = true;
    
            // I assume you know where to get the other method arguments, i.e.
            // from "LocalDirectory" on; your incomplete code example didn't
            // provide that detail. :(
            await DownloadFTPAsync(_cancelTokenSource.Token,
                LocalDirectory, RemoteFile, Login, Password);
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Download was cancelled by user.");
        }
        catch (Exception ex)
        {
            // Some other error occurred
            MessageBox.Show(ex.ToString())      
        }
        finally
        {
            downloadButton.Enabled = true;
            cancelButton.Enabled = false;
    
            _cancelTokenSource = null;
        }
    
    }
    

    如果用户点击您的取消按钮,您将像这样处理Click 事件:

    void cancelButton_Click(object sender, EventArgs e)
    {
        ftp.Abort();
        _cancelTokenSource.Cancel();
    }
    

    调用FtpWebRequest.Abort() 方法会中断任何实际的异步FTP I/O 操作,当然调用CancellationTokenSource.Cancel() 会导致CancellationToken 表明请求取消。

    【讨论】:

    • 感谢您的宝贵时间和回答,我试试看!
    • 只是一个问题,我使用的是 VS 2010,它给出了一个错误找不到类型或命名空间名称“异步”。我找到了这篇文章 [链接] (stackoverflow.com/questions/21108585/…),是真的吗??
    • @JeszSuarez:是的,确实需要 C# 5(即 VS2012 或更高版本)才能使用 async 方法。 You can get it to work in VS2010 但您可能会遇到官方版本中已修复的错误。如果可能的话最好升级到VS2012/.NET 4.5。
    • @JeszSuarez:请注意,基本思想在没有async 方法的情况下仍然有效。只是不太方便;您可以直接调用BeginGetResponse() 方法,同样可以调用Stream.BeginRead()Stream.BeginWrite() 进行异步I/O。使用Control.Invoke()执行更新UI的代码(即label1.Text = ...
    猜你喜欢
    • 1970-01-01
    • 2018-03-18
    • 2015-01-21
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    相关资源
    最近更新 更多