views:

521

answers:

3

Hi guys,

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.

+2  A: 

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.

Bernhof
In my case it works either with BackgroundWorker or with ThreadStart.
Bogdan_Ch
Thank you for the code but what exactly is "Action", is it a custom type you have built for this purpose? Could you please give me a hint on how I can implement that?
Andrei
I haven't built it, no. System.Action<T> is a generic delegate type in .NET, which is used for invoking methods which return _void_ and takes one parameter of type _T_, e.g. void SaveFile(string fileName), where _T_ in this case is _string_.
Bernhof
It became available in .NET 2.0, so if you're using .NET 1.1, you'll have to define your own delegate and use it instead of Action<T>
Bernhof
Oops, I just noticed that StackOverflow replaced the <string>-part of the Action declaration. I've updated the answer to show the full code.
Bernhof
+1  A: 

Hi,

Bernhof might be right, however, be careful. All UI elements should execute on the same thread. So if you create a new thread for the SFD, make sure you don't update any control on your main window.

Kind regards, Guillaume Hanique

Guillaume Hanique
+1  A: 

In my case debugger DO enters DoWorkDownload() It enters after the end of btnDownload_Click() Set breakpoint on SaveFileDialog sfd = new SaveFileDialog(); and it should work

In order to prove that it works asynchronously I even put the following code

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

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

and run without debugger and you will see that when current thread is going to sleep, SaveFileDialog will appear... and only after 10 seconds a message box will be shown

Bogdan_Ch