The calling thread must be STA, because many UI co

2019-02-12 11:11发布

问题:

Firstly I have read several answers to similar questions on the site but to be honest I find them a bit confusing (due to my lack of experience rather than the answers!). I am using a the FileSystemWatcher() class to monitor a folder for a file being created/changed. Once the event occurs I then want to load another form in the project. Instead of loading the form I get the error when the constructor on the new form is trying to execute. I am only using one thread - I'm not attempting to load the form under a different thread. My code is as follows

 //MainWindow
 public static void FolderWatcher()
  {
        FileSystemWatcher fsWatcher = new FileSystemWatcher();
        fsWatcher.Path = "C:\\dump";
        fsWatcher.Filter = "*";
        fsWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
        | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        fsWatcher.Created += new FileSystemEventHandler(OnChanged);
        fsWatcher.EnableRaisingEvents = true;    
  }

  public static void OnChanged(object source, FileSystemEventArgs e)
  {
       var imagePreview = new ImagePreview();
       imagePreview.Show();
  }


  //SecondForm
  public partial class ImagePreview : Window
  {
        public ImagePreview()
        {
              InitializeComponent(); //error occurs here
        }
  }

Hope you can help, many thanks in advance.

回答1:

It doesn't matter how many threads you are using. There is just a rule: any thread where you are creating UI must be STA.

In case you have only one thread, this very one has to be STA. :-) In order to make the main thread an STA, you need to use the STAThread attribute on your Main:

[STAThread]
static void Main(string[] args)
{
    // ...

If you just create a standard WPF application, the main thread is already marked with the needed attribute, so there should be no need for change.

Beware that the events from FileSystemWatcher may come from some other thread which is internally created by the framework. (You can check that by setting a breakpoint in OnChanged.) In this case, you need to forward the window creation to an STA thread. In case your application is a WPF application, it's done this way:

public static void OnChanged(object source, FileSystemEventArgs e)
{
    var d = Application.Current.Dispatcher;
    if (d.CheckAccess())
        OnChangedInMainThread();
    else
        d.BeginInvoke((Action)OnChangedInMainThread);
}

void OnChangedInMainThread()
{
    var imagePreview = new ImagePreview();
    imagePreview.Show();
}


回答2:

You're calling UI stuff from a non-UI thread (i.e. the FileSystemWatcher is firing the event from a non-UI thread).

I wrote an extension method that I used in my one and only WPF project (ugh):

public static class DispatcherEx
{
    public static void InvokeOrExecute(this Dispatcher dispatcher, Action action)
    {
        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            dispatcher.BeginInvoke(DispatcherPriority.Normal,
                                   action);
        }
    }
}

so now, after making the methods non-static so you have access to the MainWindow Dispatcher, you'd do this in your event callback:

public void OnChanged(object source, FileSystemEventArgs e)
{
    this.Dispatcher.InvokeOrExecute(()=>{
       var imagePreview = new ImagePreview();
       imagePreview.Show();
    });
}


回答3:

Use the dispatcher of the main window. Usually there is just one UI thread per application, otherwise you may get an "invalid cross-thread access" exception from WPF.

The file system watcher raises its events on a different thread and the dispatcher can be used to make that code run in the UI thread.

Use Dispatcher.BeginInvoke in that event handler and you'll be fine. :)



标签: c# wpf sta