Search code examples
winformsasynchronoussavefiledialog

How to use SaveFileDialog asynchronously?


I have a windows forms application, with a button - on the button's event handler, I need to download a file with SaveFileDialog. But I need to do this asynchronously on a separate thread.

So far, I came up with this code, but I don't know if my approach is flawed or 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
            }
        }
}

My logic in the code above is: on button click create a new thread, pass the DoWorkDownload() method to the thread, and then start it; at that moment it is supposed to enter the work method - however, when debugging it never enters DoWorkDownload().

Does anyone know what I am missing?

Thank you.


Solution

  • You can use the BackgroundWorker, which is easy to use.

    Also, I'm not sure it's completely safe (I could be wrong, though) to show the SaveFileDialog in the new thread. My recommendation would be a flow as such:

    1. Show SaveFileDialog on main thread.
    2. Pass file name to a method, which is then called asynchronously.

    Here's an example implementation, without the use of 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)
    }
    

    Note that all actions performed on non-UI threads, must only affect non-UI elements. If you want to modify UI elements, you should marshal the call back to the UI thread, by using Control.Invoke (e.g. this.Invoke). See this post for further details.