如何异步使用SaveFileDialog?

问题描述 投票:0回答:4

我有一个 Windows 窗体应用程序,带有一个按钮 - 在按钮的事件处理程序上,我需要使用 SaveFileDialog 下载文件。但我需要在单独的线程上异步执行此操作。

到目前为止,我想出了这段代码,但我不知道我的方法是否有缺陷或OK:

        private void btnDownload_Click(object sender, EventArgs e)
        {
                ThreadStart tStart = new ThreadStart(DoWorkDownload);
                Thread thread = new Thread(tStart);
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
        }

        private void DoWorkDownload()
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.InitialDirectory = "C:\\";
            sfd.Filter = "All files (*.*)|*.*";
            sfd.FilterIndex = 1;
            sfd.RestoreDirectory = true;

            if (sfd.ShowDialog() == DialogResult.OK)
            {
            //do file saving here
            }
        }
}

上面代码中我的逻辑是:单击按钮创建一个新线程,将 DoWorkDownload() 方法传递给该线程,然后启动它;在那一刻,它应该进入工作方法 - 然而,在调试时它永远不会进入 DoWorkDownload()。

有人知道我错过了什么吗?

谢谢你。

winforms asynchronous savefiledialog
4个回答
2
投票

您可以使用BackgroundWorker,它很容易使用。

另外,我不确定在新线程中显示 SaveFileDialog 是否完全安全(不过我可能是错的)。我的建议是这样的流程:

  1. 在主线程上显示 SaveFileDialog。
  2. 将文件名传递给方法,然后异步调用该方法。

这是一个示例实现,没有使用

BackgroundWorker
:

private void button1_Click(object sender, EventArgs e)
{
  SaveFileDialog sfd = new SaveFileDialog();
  sfd.InitialDirectory = "C:\\";
  sfd.Filter = "All files (*.*)|*.*";
  sfd.FilterIndex = 1;
  sfd.RestoreDirectory = true;
  if (sfd.ShowDialog() == DialogResult.OK)
  {
    // Invoke the SaveFile method on a new thread.
    Action<string> invoker = new Action<string>(SaveFile);
    invoker.BeginInvoke(sfd.FileName, OnSaveFileCompleted, invoker);
  }
}

protected void SaveFile(string fileName)
{
  // save file here (occurs on non-UI thread)
}

protected void OnSaveFileCompleted(IAsyncResult result)
{
  Action<string> invoker = (Action<string>) result.AsyncState;
  invoker.EndInvoke(result);
  // perform other actions after the file has been saved (also occurs on non-UI thread)
}

请注意,在非 UI 线程上执行的所有操作必须仅影响非 UI 元素。如果要修改 UI 元素,则应使用

Control.Invoke
(例如
this.Invoke
)将回调编组回 UI 线程。请参阅这篇文章了解更多详情。


2
投票

在我的例子中,调试器 DO 输入 DoWorkDownload() btnDownload_Click()结束后进入 在 SaveFileDialog 上设置断点 sfd = new SaveFileDialog();它应该可以工作

为了证明它是异步工作的,我什至添加了以下代码

ThreadStart tStart = new ThreadStart(DoWorkDownload);
Thread thread = new Thread(tStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

Thread.Sleep(10000);
MessageBox.Show("qwe");

并且在没有调试器的情况下运行,您将看到当当前线程将要休眠时,将出现 SaveFileDialog...并且仅在 10 秒后才会显示消息框


1
投票

伯恩霍夫可能是对的,但要小心。所有 UI 元素应在同一线程上执行。因此,如果您为 SFD 创建新线程,请确保不要更新主窗口上的任何控件。

亲切的问候, 纪尧姆·哈尼克


0
投票

我知道这是一个非常古老的问题,但由于我也遇到了同样的问题,我想分享我的现代解决方案: 修复方法只是在“await Task.Run(()”之前调用对话框:

public async Task<string> DownloadFile(string pFilter = "MyNotes document|*.note|All Files|*.*", string pDefaultExt = "note")
{
    string _ret = string.Empty;
    try
    {
        if (FilesRequest != null)
        {
            SaveFileDialog SFDialog = new SaveFileDialog()
            {
                Filter = pFilter,
                FilterIndex = 0,
                DefaultExt = pDefaultExt,
                AddExtension = true,
                CheckPathExists = true,
                OverwritePrompt = true,
                FileName = FileData.Name,
                InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
            };

            if (SFDialog.ShowDialog() == DialogResult.OK)
            {
                await Task.Run(() =>
                {
                    DownloadedFilePath = SFDialog.FileName;
                    using (var download_stream = new System.IO.FileStream(SFDialog.FileName, System.IO.FileMode.Create))
                    {
                        var DS = FilesRequest.DownloadWithStatus(download_stream);
                        if (DS.Status == Google.Apis.Download.DownloadStatus.Completed)
                        {
                            _ret = SFDialog.FileName;
                        }
                        else
                        {
                            _ret = DS.Status.ToString();
                        }
                    }
                });
            }
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
    return _ret;
}

然后这样称呼它:

this.FilePath = await API.DownloadFile();
© www.soinside.com 2019 - 2024. All rights reserved.