0

我制作了一个加载一堆计算机信息的程序。在 Form_Load 事件中,我让它初始化 3 个(该数量会增加)信息面板。具有一堆单元信息的程序似乎使程序加载相当缓慢。我试图通过从 WMI 切换到使用本机调用来加速它,这帮助了很多。很快我也会发布网络信息。我曾经加载过该面板,但我将其禁用了一段时间,直到我解决了其他面板中的错误。因此,在学习如何使用单独的线程来更新我的电池信息时,我认为我可以在我的单元信息面板中创建单独的线程,以便它可以更快地加载。我不知道我的任何信息都会导致并发问题,但我可以解决这个问题。

我想从小处着手,所以如果我改变这个怎么办

    private void Form1_Load(object sender, EventArgs e)
    {
        unitInformationPanel1.PopulateUnitInformation();
        batteryInformationPanel1.InitializeBatteries();
        magStripeReaderPanel1.SetupPointOfSale();
    }

对此

    private void Form1_Load(object sender, EventArgs e)
    {
        Thread infoThread = new Thread(new ThreadStart(unitInformationPanel1.PopulateUnitInformation));
        infoThread.Start();
        batteryInformationPanel1.InitializeBatteries();
        magStripeReaderPanel1.SetupPointOfSale();
    }

填充单元信息完成后,信息线程会终止吗?还是将该线程创建移到 PopulateUnitInformation 中会更好?这是它的样子。

    public void PopulateUnitInformation()
    {
        unitModelLabel.Text = Properties.Settings.Default.UnitModelString;
        serialNumberLabel.Text = Properties.Settings.Default.UnitSerialString;
        biosVersionLabel.Text = UnitBios.GetBiosNumber();
        osLabel.Text = OS.getOSString();
        cpuLabel.Text = UnitCpu.GetCpuInfo();

        var hdd = HddInfo.GetHddInfo();
        diskNameLabel.Text = hdd.Name;
        diskCapacityLabel.Text = hdd.Capacity;
        diskFirmwareLabel.Text = hdd.Firmware;
        memoryLabel.Text = MemoryInformation.GetTotalMemory();
        NetworkPresenceInformation.GetAdapatersPresent();
        biometricLabel.Text = BiometricInformation.IsPresent ? "Present" : "Not Present";
        var networkAdaptersPresense = NetworkPresenceInformation.GetAdapatersPresent();
        bluetoothLabel.Text = networkAdaptersPresense[0] ? "Present" : "Not Present";
        wifiLabel.Text = networkAdaptersPresense[1] ? "Present" : "Not Present";
        cellularLabel.Text = networkAdaptersPresense[2] ? "Present" : "Not Present";
    }

--

哇,我只是用信息线程运行它,它仍然需要一些时间来加载(可能是我在主线程中创建的 12 个面板。但它加载了 12 个帧,并且单元信息面板在加载所有内容后填充了它的信息。这很酷, 但它安全吗?为我的面板制作 12 个线程是否容易一些?还是很愚蠢?

编辑

这就是我为秒表所做的。

    Stopwatch programTimer;
    public Form1()
    {
        programTimer = Stopwatch.StartNew();
        InitializeComponent();
        SetupDebugWindow();
        TerminateKeymon();
        UnitModel.SetModel();
        UnitSerialNumber.SetSerialNumber();
    }
    private void Form1_Shown(object sender, EventArgs e)
    {
        audioBrightnessPanel1.UpdateBrightnessTrackbar();
        applicationLauncherPanel1.LoadApplications();
        programTimer.Stop();
        Console.WriteLine("Load Time: {0}",programTimer.ElapsedMilliseconds);
        timer1.Start();
    }

这会准确吗?

编辑 2 2012 年 6 月 18 日

好吧,我接受了使用 backgroundworker 的建议。如果我做对了,请告诉我。

    private void Form1_Load(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }
    void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        unitInformationPanel1.PopulateUnitInformation();
        batteryInformationPanel1.InitializeBatteries();
        magStripeReaderPanel1.SetupPointOfSale();
    }
4

3 回答 3

7

You've asked a very broad question, but I'm going to give some general advice. If you want more specific information, you should consider deleting this question and posting more specific individual questions.

  1. First and foremost, you should very strongly consider using something like the System.Threading.Task class for your multithreaded operations. There is a ton of information online about how to get started with it and how you can use Tasks to manage asynchronous operations. The short story is that if you're spinning up your own thread (as you're doing above), you almost certainly should be using something else to do that for you.

  2. Adding multithreading to your code will not, in the strictest sense of the word, make it any "faster"; they will always take the same amount of total processor time. What it can and will do is two things: free up the UI thread to be responsive and allow you to split that "total processor time" across multiple cores or processors, should those be available to the system. So, if you have operation X that takes 10 seconds to complete, then just shifting operation X to another thread will not make it complete any faster than 10 seconds.

  3. No, what you are doing above is not safe. I'm assuming that somewhere you've turned off checking for cross-thread communication errors in your app? Otherwise, that code should throw an exception, assuming this is a WinForms or WPF application. This is one reason to use Tasks, as you can easily separate the part of your process that actually takes a long time (or isn't UI related), then add a task continuation that uses the results and populates the UI elements within a properly synchronized context.

于 2012-06-14T16:11:11.253 回答
2

So my final approach this was as follows. I felt that my Main Form was doing more than it should. Sticking with the single responsibility principle I decided that MainForm should only be responsible for one thing, showing and displaying all 12 panels (now down to 11, i turned one into a menu item). So moved all the multithreading out of mainform and into program.cs. I found that this was even a little more difficult. What I did find though was a simple solution that allows me to not even worry about multithreading at all. It was the Idle event. Here is what i chose to do.

        [STAThread]
    static void Main()
    {
        DateTime current = DateTime.Now;
        DateTime today = new DateTime(2012,7,19);
        TimeSpan span = current.Subtract(today);
        if (span.Days<0)
        {
            MessageBox.Show("Please adjust Time then restart Aspects","Adjust Time");
            Process.Start("timedate.cpl").WaitForExit();
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Idle += new EventHandler(Application_Idle);

            mainForm = new MainForm();
            mainForm.Closing += new CancelEventHandler(mainForm_Closing);

            #if !DEBUG
            TerminateKeymon();
            StartSerial();
            SetupDefaultValues();
            EmbeddedMessageBox(0);
            #endif

            Application.Run(mainForm);
        }
    }

    static void Application_Idle(object sender, EventArgs e)
    {
        Application.Idle -= Application_Idle;
        mainForm.toolStripProgressBar1.Increment(1);
        UnitInformation.SetupUnitInformation();
        mainForm.toolStripProgressBar1.Increment(1);
        Aspects.Unit.HddInfo.GetHddInfo();
        mainForm.toolStripProgressBar1.Increment(1);

        for (int i = 0; i < mainForm.Controls.Count; i++)
        {
            if (mainForm.Controls[i] is AbstractSuperPanel)
            {
                try
                {
                    var startMe = mainForm.Controls[i] as AbstractSuperPanel;
                    startMe.StartWorking();
                    mainForm.toolStripProgressBar1.Increment(1);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + mainForm.Controls[i].ToString());
                }
            }
        }
        mainForm.toolStripProgressBar1.Value = 0;
    }

to sum up what that does is is I add a idle listener event. Once the thead goes idle (basically meaning that Mainform is finished drawing and making all 12 panels and is showing on my desktop) I then kill the idle event listener and tell all my panels and classes to start working one at a time, updating my progress bar as I go. It works great. The load time is still the same as it was before, but there is window visibile after only a few seconds. Maybe not the best use of resources, but i think the solution is simple and straight forward.

于 2012-09-22T15:52:03.387 回答
1

I had a question somewhat related to this for Mobile app development a few months back (see How to write a Trigger?), and Marc "the man" Gravell posted back with a simple class that I modified to return data to my main application whenever the thread was complete.

The actual class I put into use has loads of pointless data (for you), so I'm going to paste in a revised version of Mr. Gravell's code using techniques which I used to make them work:

First, I had to create my own EventArgs class:

public class SuperEventArgs : EventArgs {

  private object data;

  public SuperEventArgs(object data) : base() {
    this.data = data;
  }

  public object Data { get { return data; } }

}

Using that, here is a class I created to pass my data back to the main thread:

public delegate event DataChangedHandler(object sender, SuperEventArgs e);

public class Simple1 {

  private object parameter1, parameter2;
  private Control parent;

  #if PocketPC
  public delegate void MethodInvoker(); // include this if it is not defined
  #endif

  public Simple1(Control frmControl, object param1, object param2) {
    parent = frmControl;
    parameter1 = param1;
    parameter2 = param2;
  }

  public event DataChangedHandler DataChanged;

  public void Start() {
    object myData = new object(); // whatever this is. DataTable?
    try {
      // long routine code goes here
    } finally {
      if (DataChanged != null) {
        SuperEventArgs e = new SuperEventArgs(myData);
        MethodInvoker methInvoker = delegate {
          DataChanged(this, e);
        };
        try {
          parent.BeginInvoke(methInvoker);
        } catch (Exception err) {
          Log(err); // something you'd write
        }
      }
    }
  }

}

Back in the actual main thread of execution, you'd do something like this:

public partial class Form1 : Form {

  private Simple1 simple;

  public Form1() {
    object query = new object(); // something you want to pass in
    simple = new Simple1(this, query, DateTime.Now);
    simple.DataChanged += new DataChangedHandler(simple1_DataChanged);
    Thread thread = new Thread(simpleStart);
    thread.Start();
  }

  private void simpleStart() {
    if (simple != null) {
      simple.Start();
    }
  }

  private void simple1_DataChanged(object sender, SuperEventArgs e) {
    MyFancyData fancy = e.Data as MyFancyData;
    if (fancy != null) {
      // populate your form with the data you received.
    }
  }
}

I know it looks long, but it works really well!

This is not anything I have actually tested, of course, because there isn't any data. If you get to working with it and you experience any issues, let me know and I'll happily help you work through them.

~JoeP

于 2012-06-14T19:35:08.147 回答