2009-09-18 6 views
1

ОБНОВЛЕНИЕ: Я вставил весь источник для г-на Крафта. Сказав это, это, вероятно, мое первое настоящее приложение WinForms, поэтому, пожалуйста, проанализируйте и проанализируйте, если вам это нравится. Кроме того, чтобы сделать критику менее болезненным для меня, я должен отметить, что я написал это приложение очень быстро, поэтому у меня есть задача рефакторинга и улучшения дизайна для него, назначенного мне :)Быть способным отлаживать приложение WinForms и избегать использования графического интерфейса от замораживания

Так что просто немного фона. Я веб-разработчик .NET, но мне нужно было создать настольное приложение для наших разработчиков с некоторыми обычными задачами, которые они выполняют каждый день, чтобы ускорить производительность. Ответ на это может быть очень очевиден, но я едва коснулся WinForms, поэтому, пожалуйста, со мной.

Я создал приложение C# WinForms (.NET 2.0) и все работает, но у меня есть некоторые проблемы. Когда я изначально написал это, пользовательский интерфейс был слабым, поэтому, естественно, я решил создать поток, чтобы сделать вещи более текучими. Это сработало, однако я не могу отлаживать приложение из-за этого потока. Приложение прерывается в VS.NET, когда я его отлаживаю. VS.Net замерзает. Я продолжаю читать о потоке пользовательского интерфейса, но понятия не имею, как получить к нему доступ, и я не понимаю его в стороне от того факта, что, скорее всего, что-то связанное с UI должно быть в этой теме.

Я включил фрагмент кода, показывающий, что я сделал, чтобы заставить пользовательский интерфейс останавливаться от замораживания.

using System; 
using System.Text; 
using System.Windows.Forms; 
using System.Diagnostics; 
using System.IO; 
using System.Threading; 
using System.Text.RegularExpressions; 

namespace Acuity.Tools 
{ 
    /// <summary> 
    /// A form for managing Acuity development databases. 
    /// </summary> 
    public partial class DeveloperTools : Form 
    {   
     #region Public Constructors 
     public DeveloperTools() 
     { 
      InitializeComponent(); 
     } 
     #endregion 

     #region Private Constants 
     private const string Scripts1To50Region = "#region 1 to 50#"; 
     private const string Scripts51AndUpRegion = "#region 51+#";     
     private const string DefaultSanitizedPassword = "t78"; 

     private const string UseSanitizedPasswordTemplateVariable = "%%UseSanitizedPassword%%"; 
     private const string DatabaseToRestoreTemplateVariable = "%%DatabaseToRestore%%"; 
     private const string DatabaseToRestoreToTemplateVariable = "%%DatabaseToRestoreTo%%"; 
     private const string SanitizedPasswordTemplateVariable = "%%sanitizedPassword%%"; 
     private const string StarKeyTemplateVariable = "%%StarKey%%"; 
     #endregion 

     #region Private Methods 
     /// <summary> 
     /// Starts the database restore. 
     /// </summary> 
     /// <param name="sender"></param> 
     /// <param name="e"></param> 
     private void RestoreDatabaseButton_Click(object sender, EventArgs e) 
     { 
      RestoreDatabaseButton.Enabled = false; 
      LogViewer.Text = string.Empty; 
      UseWaitCursor = true; 

      // Need to spawn a thread or else the UI freezes. 
      Thread restoreDatabase = new Thread(new ThreadStart(RestoreDatabase)); 
      restoreDatabase.Start(); 
     } 

     /// <summary> 
     /// Starts the database restore. 
     /// </summary> 
     private void RestoreDatabase() 
     { 
      error = false; 

      // TODO: Database restores fine, but it looks like an error is logged here. Not sure why. 
      // for now I just ignore the errors. Will need to look into it. 
      if (RestoreDatabaseCheckbox.Checked) 
      { 
       RestoreProductionDbToDevelopmentDb(); 
      } 

      // TODO: The error reporting is a little buggy at the moment, It logs errors in a couple of cases even though 
      // things work. Perhaps John can look at his dbobjects.vbs script at some point to see why an error is logged. 
      if (!error) 
      { 
       if (GenerateDatabaseObjectsCheckbox.Checked) 
       { 
        GenerateUpdatedDatabaseObjects(); 
       } 

       if (!error) 
       { 
        if (RunScriptsCheckbox.Checked) 
        { 
         RunVersioningScripts(); 
        } 
       } 
      } 

      RestoreCompleted(); 
     } 

     /// <summary> 
     /// 
     /// </summary> 
     private void RestoreCompleted() 
     { 
      if (error) 
      { 
       MessageBox.Show("There were some errors!", "Error"); 
      } 
      else 
      { 
       MessageBox.Show("Done!", "Done"); 
      } 

      UseWaitCursor = false; 
      RestoreDatabaseButton.Enabled = true; 
      LogViewer.Enabled = true; 
     } 

     /// <summary> 
     /// Runs versioning scripts on a development database. 
     /// </summary> 
     private void RunVersioningScripts() 
     { 
      LogViewer.Text += "Running versioning scripts..." + Environment.NewLine; 

      FileInfo versioningScriptsBatchFileName = new FileInfo(BatchFileForVersioningScripts.Text); 

      FileInfo scriptFileName = new FileInfo(applicationRootFolderPath + @"\Temp\" + (Guid.NewGuid()).ToString() + ".bat"); 

      StringBuilder fileContents = new StringBuilder(); 
      string drive = Regex.Match(versioningScriptsBatchFileName.Directory.FullName, "^[a-zA-Z]{1,1}").Value; 
      fileContents.Append(drive + ":\n"); 
      fileContents.AppendFormat("cd \"{0}\"\n", versioningScriptsBatchFileName.Directory.FullName); 
      fileContents.AppendFormat("\"{0}\" %1 %2 %3 %4 \"{1}\"\n", versioningScriptsBatchFileName.Name, applicationRootFolderPath + @"\Temp\DBObjects"); 

      File.WriteAllText(scriptFileName.FullName, fileContents.ToString()); 

      using (Process process = new Process()) 
      { 
       process.StartInfo.UseShellExecute = false; 
       process.StartInfo.RedirectStandardOutput = true; 
       process.StartInfo.RedirectStandardError = true; 
       process.StartInfo.CreateNoWindow = true; 
       process.StartInfo.FileName = scriptFileName.FullName; 
       process.StartInfo.Arguments = string.Format(
         "{0} {1} {2} {3}", 
         SqlLogin.Text, 
         SqlPassword.Text, 
         DatabaseServer.Text, 
         DatabaseToRestoreTo.Text 
        ); 

       process.OutputDataReceived 
        += new DataReceivedEventHandler(OutputDataHandler); 
       process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler); 
       process.Exited += new EventHandler(ProcessExited); 
       process.EnableRaisingEvents = true; 
       process.Start(); 
       process.BeginErrorReadLine(); 
       process.BeginOutputReadLine(); 

       while (!process.HasExited) 
       { 
        Console.WriteLine("Still running"); 
        Thread.Sleep(3000); 
       } 
      } 

      scriptFileName.Delete(); 
      LogViewer.Text += "Finished running versioning scripts."; 
     } 

     /// <summary> 
     /// Restores a production database to a development database. 
     /// </summary> 
     private void RestoreProductionDbToDevelopmentDb() 
     { 
      string restoreScriptTemplate = File.ReadAllText(applicationRootFolderPath + @"\Resources\RestoreProdDBToDevelopment.template"); 
      string restoreScript = restoreScriptTemplate.Replace(DatabaseToRestoreTemplateVariable, DatabaseToRestore.Text).Replace(DatabaseToRestoreToTemplateVariable, DatabaseToRestoreTo.Text); 

      restoreScript = restoreScript.Replace(UseSanitizedPasswordTemplateVariable, UseSanitizePassword.Checked ? "1" : "0"); 

      if (UseSanitizePassword.Checked) 
      { 
       restoreScript = restoreScript.Replace(SanitizedPasswordTemplateVariable, SanitizedPassword.Text ?? DefaultSanitizedPassword); 
      } 

      restoreScript = restoreScript.Replace(StarKeyTemplateVariable, UseCustomStarKey.Checked ? StarKey.Text : DatabaseToRestore.Text + "Pwd"); 

      string restoreScriptFileName = applicationRootFolderPath + @"\Temp\" + (Guid.NewGuid()).ToString() + ".sql"; 

      File.WriteAllText(restoreScriptFileName, restoreScript); 

      using (Process process = new Process()) 
      { 
       process.StartInfo.UseShellExecute = false; 
       process.StartInfo.RedirectStandardOutput = true; 
       process.StartInfo.RedirectStandardError = true; 
       process.StartInfo.CreateNoWindow = true; 
       process.StartInfo.FileName = "sqlcmd"; 
       process.StartInfo.Arguments = string.Format(
         "-U {0} -P {1} -S {2} -d {3} -i \"{4}\" -k -b", 
         SqlLogin.Text, 
         SqlPassword.Text, 
         "super-secret-database-server", 
         "master", 
         restoreScriptFileName 
        ); 

       process.OutputDataReceived 
        += new DataReceivedEventHandler(OutputDataHandler); 
       process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler); 
       process.Exited += new EventHandler(ProcessExited); 
       process.EnableRaisingEvents = true; 
       process.Start(); 
       process.BeginErrorReadLine(); 
       process.BeginOutputReadLine(); 

       while (!process.HasExited) 
       { 
        Console.WriteLine("Still running"); 
        Thread.Sleep(3000); 
       } 
      } 

      File.Delete(restoreScriptFileName); 
     } 

     /// <summary> 
     /// Regenerates all database objects that will be updated in the development database. 
     /// </summary> 
     private void GenerateUpdatedDatabaseObjects() 
     { 
      LogViewer.Text += string.Format(
        "Generating updated database objects from folder {0}", 
        DatabaseObjectsFolderPath.Text) + Environment.NewLine; 

      FileInfo updateDatabaseObjectsScriptFileName = new FileInfo(applicationRootFolderPath + @"\Resources\dbobjscripts.vbs"); 
      FileInfo scriptFileName = new FileInfo(string.Format(@"{0}\Temp\{1}.bat", applicationRootFolderPath, Guid.NewGuid())); 

      StringBuilder fileContents = new StringBuilder(); 
      string drive = Regex.Match(scriptFileName.Directory.FullName, "^[a-zA-Z]{1,1}").Value; 
      fileContents.Append(drive + ":\n"); 
      fileContents.AppendFormat("cd \"{0}\"\n", scriptFileName.Directory.FullName); 

      string dateToStartGeneratingFrom = string.Format("{0:yyyy/MM/dd}", GenerateDBObjectsDate.Value); 

      LogViewer.Text += "Database objects will be updated starting from " + dateToStartGeneratingFrom; 

      fileContents.AppendFormat("\"{0}\" {1} \"{2}\"\n", updateDatabaseObjectsScriptFileName.FullName, dateToStartGeneratingFrom, DatabaseObjectsFolderPath.Text); 
      File.WriteAllText(scriptFileName.FullName, fileContents.ToString()); 

      using (Process process = new Process()) 
      { 
       process.StartInfo.UseShellExecute = false; 
       process.StartInfo.RedirectStandardOutput = true; 
       process.StartInfo.RedirectStandardError = true; 
       process.StartInfo.CreateNoWindow = true; 
       process.StartInfo.FileName = scriptFileName.FullName; 

       process.OutputDataReceived 
        += new DataReceivedEventHandler(OutputDataHandler); 
       process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler); 
       process.Exited += new EventHandler(ProcessExited); 
       process.EnableRaisingEvents = true; 
       process.Start(); 
       Cursor.Current = Cursors.WaitCursor; 
       process.BeginErrorReadLine(); 
       process.BeginOutputReadLine(); 

       while (!process.HasExited) 
       { 
        Console.WriteLine("Still running"); 
        Thread.Sleep(3000); 
       } 
      } 

      scriptFileName.Delete(); 
      LogViewer.Text += "Finished generating updated database objects." + Environment.NewLine; 
     } 

     /// <summary> 
     /// Raised when a database restore process has exited. 
     /// </summary> 
     /// <param name="sender">A <see cref="Process"/> that is exiting.</param> 
     /// <param name="e">A <see cref="EventArgs"/>.</param> 
     private void ProcessExited(object sender, EventArgs e) 
     { 
      Console.WriteLine("Exiting"); 
     } 

     /// <summary> 
     /// Catches the redirected error stream for Yui Compressor. 
     /// </summary> 
     /// <param name="sender">A <see cref="Process"/> that is currently executing.</param> 
     /// <param name="e">A <see cref="DataReceivedEventArgs"/>.param> 
     private void YuicErrorDataHandler(object sender, DataReceivedEventArgs e) 
     {    
      if (!string.IsNullOrEmpty(e.Data)) 
      { 
       error = true; 
       CompressionLogViewer.Text += e.Data + Environment.NewLine; 
      } 
     } 

     /// <summary> 
     /// Catches the redirected error stream. 
     /// </summary> 
     /// <param name="sender">A <see cref="Process"/> that is currently executing.</param> 
     /// <param name="e">A <see cref="DataReceivedEventArgs"/>.param> 
     private void ErrorDataHandler(object sender, DataReceivedEventArgs e) 
     {    
      if (!string.IsNullOrEmpty(e.Data)) 
      { 
       error = true; 
       LogViewer.Text += e.Data + Environment.NewLine; 
      } 
     } 

     /// <summary> 
     /// Catches the redirected output stream for Yui Compressor. 
     /// </summary> 
     /// <param name="sender">A <see cref="Process"/> that is currently executing.</param> 
     /// <param name="e">A <see cref="DataReceivedEventArgs"/>.param> 
     private void YuicOutputDataHandler(object sender, DataReceivedEventArgs e) 
     { 
      if (!string.IsNullOrEmpty(e.Data)) 
      { 
       CompressionLogViewer.Text += e.Data + Environment.NewLine; 
      } 
     } 

     /// <summary> 
     /// Catches the redirected output stream. 
     /// </summary> 
     /// <param name="sender">A <see cref="Process"/> that is currently executing.</param> 
     /// <param name="e">A <see cref="DataReceivedEventArgs"/>.param> 
     private void OutputDataHandler(object sender, DataReceivedEventArgs e) 
     { 
      if (!string.IsNullOrEmpty(e.Data)) 
      { 
       LogViewer.Text += e.Data + Environment.NewLine; 

       if (e.Data.ToLower().Contains("login failed")) 
       { 
        error = true; 
        return; 
       } 
      } 
     } 

     /// <summary> 
     /// Closes the application via the File->Exit menu item. 
     /// </summary> 
     /// <param name="sender"></param> 
     /// <param name="e"></param> 
     private void ExitToolStripMenuItem_Click(object sender, EventArgs e) 
     { 
      Close(); 
     } 

     /// <summary> 
     /// Raised when the Browse... button for selecting the database objects 
     /// folder is clicked. 
     /// </summary> 
     /// <param name="sender">A <see cref="Button"/> control.</param> 
     /// <param name="e">The <see cref="EventArgs" />.</param> 
     private void DatabaseObjectsFolderPath_Click(object sender, EventArgs e) 
     { 
      SelectFolderDialog.ShowDialog(); 
      DatabaseObjectsFolderPath.Text = SelectFolderDialog.SelectedPath; 
     } 

     /// <summary> 
     /// Raised when the Browse... button for selecting a batch file to run versioning scripts 
     /// is clicked. 
     /// </summary> 
     /// <param name="sender">A <see cref="Button"/> control.</param> 
     /// <param name="e">The <see cref="EventArgs" />.</param> 
     private void BatchFileForVersioningScripts_Click(object sender, EventArgs e) 
     { 
      batchFileDialog.ShowDialog(); 
      BatchFileForVersioningScripts.Text = batchFileDialog.FileName; 
     } 

     /// <summary> 
     /// Raised when the Run Versioning Scripts checkbox is checked/unchecked. 
     /// </summary> 
     /// <param name="sender">A <see cref="Checkbox" /> control.</param> 
     /// <param name="e">The <see cref="EventArgs"/>.</param> 
     private void RunScriptsCheckbox_CheckedChanged(object sender, EventArgs e) 
     { 
      DatabaseObjectsGroupBox.Enabled = RunScriptsCheckbox.Checked; 
      SqlLogin.Enabled = RunScriptsCheckbox.Checked; 
      SqlPassword.Enabled = RunScriptsCheckbox.Checked; 
      DatabaseToRestoreTo.Enabled = RunScriptsCheckbox.Checked; 
     } 

     /// <summary> 
     /// Raised when the Generate Database Objects checkbox is checked/unchecked. 
     /// </summary> 
     /// <param name="sender">A <see cref="Checkbox" /> control.</param> 
     /// <param name="e">The <see cref="EventArgs"/>.</param> 
     private void GenerateDatabaseObjectsCheckbox_CheckedChanged(object sender, EventArgs e) 
     { 
      BatchFileGroupBox.Enabled = GenerateDatabaseObjectsCheckbox.Checked; 
     } 

     /// <summary> 
     /// Raised when the Restore Database checkbox is checked/unchecked. 
     /// </summary> 
     /// <param name="sender">A <see cref="Checkbox" /> control.</param> 
     /// <param name="e">The <see cref="EventArgs"/>.</param> 
     private void restoreDatabaseCheckbox_CheckedChanged(object sender, EventArgs e) 
     { 
      DatabaseToRestore.Enabled = RestoreDatabaseCheckbox.Checked; 
     } 

     /// <summary> 
     /// Opens a log file to select a log file for analysis. 
     /// </summary> 
     /// <param name="sender"></param> 
     /// <param name="e"></param> 
     private void SelectLogFile_Click(object sender, EventArgs e) 
     { 
      openLogFileDialog.ShowDialog(); 
      AnalyzeLogFile(openLogFileDialog.FileName); 
     } 

     /// <summary> 
     /// Analyzes a log file to see if there are any errors. 
     /// </summary> 
     /// <param name="fileName"></param> 
     private void AnalyzeLogFile(string fileName) 
     { 
      if (!string.IsNullOrEmpty(fileName)) 
      { 
       using (Stream file = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
       { 
        if (file != null) 
        { 
         using (StreamReader sr = new StreamReader(file)) 
         { 
          string text = sr.ReadToEnd(); 
          sr.Close(); 

          MatchCollection matches = errorLocator.Matches(text); 
          StringBuilder sb = new StringBuilder(); 

          if (0 < matches.Count) 
          { 
           foreach (Match match in matches) 
           { 
            sb.AppendLine(match.Groups["error"].Value); 
           } 

           ErrorsFound.Text = sb.ToString(); 
          } 
          else 
          { 
           ErrorsFound.Text = "No errors found in log file!"; 
          } 
         } 

         file.Close(); 
        } 
       } 
      } 
     } 

     /// <summary> 
     /// Reloads the error log. 
     /// </summary> 
     private void RefreshErrorLog() 
     { 
      AnalyzeLogFile(openLogFileDialog.FileName); 
     } 

     private void RefreshLogFile_Click(object sender, EventArgs e) 
     { 
      RefreshErrorLog(); 
     } 

     private void BrowseFileToCompress_Click(object sender, EventArgs e) 
     { 
      selectJSOrCssFile.ShowDialog(); 
      FileToCompress.Text = selectJSOrCssFile.FileName; 
     } 

     private void StarUbcNickTesting(object sender, EventArgs e) 
     { 
      selectYuicFileName.ShowDialog(); 
      YuicFilePath.Text = selectYuicFileName.FileName; 
     } 

     private void BrowseFileToCompressTo_Click(object sender, EventArgs e) 
     { 
      selectJSOrCssFile.ShowDialog(); 
      FileToCompressTo.Text = selectJSOrCssFile.FileName; 
     } 

     private void GenerateCompressedFile_Click(object sender, EventArgs e) 
     { 
      Thread startCompression = new Thread(new ThreadStart(CompressedFile)); 
      startCompression.Start(); 
     } 

     private void CompressedFile() 
     { 
      error = false; 
      CompressionLogViewer.Text = ""; 

      if (string.IsNullOrEmpty(YuicFilePath.Text)) 
      { 
       CompressionLogViewer.Text += "You need to specify the path to Yui Compressor" + Environment.NewLine; 
       return; 
      } 

      if (string.IsNullOrEmpty(FileToCompress.Text)) 
      { 
       CompressionLogViewer.Text += "You need to select a file to compress." + Environment.NewLine; 
       return; 
      } 

      if (string.IsNullOrEmpty(FileToCompressTo.Text)) 
      { 
       CompressionLogViewer.Text += "You need to select a file to compress to." + Environment.NewLine; 
       return; 
      } 

      CompressionLogViewer.Text = string.Format(
       "Compressing file {0} to {1}", 
       FileToCompress.Text, 
       FileToCompressTo.Text 
       ) + Environment.NewLine; 

      using (Process process = new System.Diagnostics.Process()) 
      { 
       process.StartInfo.UseShellExecute = false; 
       process.StartInfo.RedirectStandardOutput = true; 
       process.StartInfo.RedirectStandardError = true; 
       process.StartInfo.CreateNoWindow = true; 
       process.StartInfo.FileName = "java"; 
       process.StartInfo.Arguments = string.Format(
        "-jar \"{0}\" -o \"{1}\" \"{2}\"", 
        YuicFilePath.Text, 
        FileToCompressTo.Text, 
        FileToCompress.Text 
        ); 

       process.OutputDataReceived 
        += new DataReceivedEventHandler(YuicOutputDataHandler); 
       process.ErrorDataReceived += new DataReceivedEventHandler(YuicErrorDataHandler); 
       process.Exited += new EventHandler(ProcessExited); 
       process.EnableRaisingEvents = true; 
       process.Start(); 
       Cursor.Current = Cursors.WaitCursor; 
       process.BeginErrorReadLine(); 
       process.BeginOutputReadLine(); 

       while (!process.HasExited) 
       { 
        Console.WriteLine("Still running"); 
        Thread.Sleep(3000); 
       } 

       if (!error) 
       { 
        CompressionLogViewer.Text += "The file has been compressed."; 
       } 
      } 
     } 

     private void BrowseForVersioningScriptsFolder_Click(object sender, EventArgs e) 
     { 
      SelectFolderDialog.ShowDialog(); 

      if (!string.IsNullOrEmpty(SelectFolderDialog.SelectedPath)) 
      { 
       VersioningScriptsFolderPath.Text = SelectFolderDialog.SelectedPath; 
      } 

      BindFileTreeView(); 
     } 

     private void BindFileTreeView() 
     { 
      if (!string.IsNullOrEmpty(VersioningScriptsFolderPath.Text) && 
       Directory.Exists(VersioningScriptsFolderPath.Text)) 
      { 
       string[] versioningScriptFiles = Directory.GetFiles(VersioningScriptsFolderPath.Text, "*.sql"); 
       fileTreeView.Nodes.Clear(); 
       fileTreeView.CheckBoxes = true; 
       SelectAll.Checked = true; 

       foreach (string fullFileName in versioningScriptFiles) 
       { 
        TreeNode node = new TreeNode(Path.GetFileName(fullFileName)); 
        node.Checked = true; 
        node.SelectedImageKey = fullFileName; 
        fileTreeView.Nodes.Add(node); 
       } 
      } 
     } 

     private void GenerateVersioningScriptsBatchFile_Click(object sender, EventArgs e) 
     { 
      VersioningScriptsLogViewer.Text = string.Empty; 

      if (string.IsNullOrEmpty(VersioningScriptsFolderPath.Text)) 
      { 
       VersioningScriptsLogViewer.Text += "Please select a versioning scripts folder."; 
       return; 
      } 

      DirectoryInfo versioningScriptFolder = new DirectoryInfo(VersioningScriptsFolderPath.Text); 
      FileInfo versioningScriptsBatchFile = new FileInfo(string.Format(@"{0}\Update{1}.bat", versioningScriptFolder.FullName, versioningScriptFolder.Name)); 

      // Remove existing update batch file if any. 
      if (versioningScriptsBatchFile.Exists) 
      { 
       versioningScriptsBatchFile.Delete(); 
       VersioningScriptsLogViewer.Text += "Removing existing versioning scripts batch file " + versioningScriptsBatchFile.FullName + Environment.NewLine; 
      } 

      VersioningScriptsLogViewer.Text += "Generating versioning scripts batch file " + versioningScriptsBatchFile.FullName + Environment.NewLine; 

      script1To50 = new StringBuilder(); 
      script51AndUp = new StringBuilder(); 

      foreach (TreeNode versioningScript in fileTreeView.Nodes) 
      { 
       if (versioningScript.Checked) 
       { 
        AddVersioningScriptToBatchFile(new FileInfo(versioningScript.SelectedImageKey)); 
       } 
      } 

      string templateFile = File.ReadAllText(applicationRootFolderPath + @"\Resources\VersioningScriptsBatchFile.template"); 
      string newBatchFileContents = templateFile.Replace(Scripts1To50Region, script1To50.ToString()); 
      newBatchFileContents = newBatchFileContents.Replace(Scripts51AndUpRegion, script51AndUp.ToString()); 

      File.WriteAllText(versioningScriptsBatchFile.FullName, newBatchFileContents); 
+0

Я не вижу ничего очевидного; и у меня нет проблем с зависанием VS на меня с небольшим кодом, который вы предоставили. Вы должны уметь поставить точку останова в методе RestoreDatabase, и она остановится на нем, когда выполнение ударит по нему. Возможно, если вы разместили код в методе RestoreDatabase. –

+2

В качестве примечания вы также можете запускать потоки в WinForms с помощью BackgroundWorker. – Powerlord

+0

Г-н Крафт, я вложил полный источник. – nickytonline

ответ

2

Всякий раз, когда вы создаете обработчик событий для своей формы или контролируете, он запускается в потоке пользовательского интерфейса. Поток пользовательского интерфейса в приложении для создания выигрышей. часто также называют основной прикладной нитью. Это поток, выполняющий цикл сообщения Application.Run().

Я нахожу это в отношении того, что вы говорите, что вы «естественно решили создать нить». Внедрение дополнительных потоков почти гарантированно увеличивает сложность и затрудняет отладку приложения, как вы сразу обнаружили.

Вы сказали, что у вас неопытные в не-веб-приложениях, поэтому зачем вводить сложность потоковой обработки поверх кривой обучения Windows Forms? Попробуйте немного сложнее заставить его работать без использования потоков, а затем рассмотрите потоки позже, если это все еще необходимо.

В формах Windows часто можно избежать потоков с помощью таймера и/или приложения.DoEvents(). Я не говорю, что не использую потоки, просто я видел много приложений, где даже нет смысла разрешать пользователю продолжать работать, пока процесс не завершен.

Если вы действительно хотите придерживаться нитей, я также настоятельно рекомендую использовать класс BackgroundWorker. Он прекрасно инкапсулирует рабочий поток и обеспечивает хорошую безопасную связь, такую ​​как события ProgressChanged и RunWorkerCompleted.

Глядя на ваш код, я также предлагаю прочитать некоторые сведения о доступе к элементам пользовательского интерфейса из рабочих потоков. Короче говоря, не делайте этого, если не используется Control.Invoke() или Control.BeginInvoke(). Этот previous StackOverflow question был бы хорошим началом.

[UPDATE]

Есть целый ряд мест, где ваш код установки или проверки свойств элемента управления пользовательского интерфейса непосредственно из рабочего потока. Чтобы избежать исключений и неопределенного поведения, вы должны всегда обращаться к элементам управления пользовательского интерфейса из потока, который их создал, т.е. UI/Основной поток. Использование Invoke() или BeginInvoke() позволяет сделать это безопасно.

+0

У меня смешанное чувство об этом ответе. Мне нравится объяснение того, что представляет собой поток пользовательского интерфейса, но спрашивать, почему поток вводится, кажется, делает шаг назад. Короткий ответ заключается в том, что Windows требует, чтобы он поддерживал графический интерфейс, поскольку он использует поток GUI, чтобы делать такие вещи, как краска/перерисовка окон, управление насосом сообщений и т. Д. ... Посмотрите, что говорит Джон Скит здесь: http: // stackoverflow .com/questions/1035439/database-access-in-gui-thread-bad-isnt-it/1035451 # 1035451 – Powerlord

+0

@R Bemrose, представляя темы приложения, где они не добавляют реальной выгоды, или может быть жизнеспособные альтернативы - БОЛЬШОЙ нет-нет. Когда вы сочетаете это с неопытными в Windows Forms, это рецепт проблем. Помните KISS и «простейшую вещь, которая могла бы работать»? – Ash

+0

@R Bemrose, в этом ответе Джон говорит о невосприимчивом графическом интерфейсе: «В некоторых случаях, особенно для« быстрых и грязных »инструментов, это на самом деле правильный выбор». Реальная проблема заключается в том, что если вы используете потоки, ваша сложность отладки, тестирования и обслуживания автоматически увеличивается. – Ash

0

Как ваша нить публикует информацию обратно в основной поток? Я смог отладить многопоточные приложения, поставив точки останова в рабочий поток раньше.

Действительно, вы должны сделать бит, который выполняется в отдельной ветке, которую можно проверить на своем собственном. Таким образом, вы можете отлаживать его изолированно и не беспокоиться о проблемах между потоками.

+0

@Cage - правда о том, чтобы сделать его проверяемым. Я быстро взломал его, но обязательно это сделаю. – nickytonline

0

Пользовательский интерфейс (интерфейс пользователя) поток основной поток в приложении отвечает за обработку окна сообщений, такие как щелчок мыши, ввод с клавиатуры, перерисовки экрана и т.д.

Так поток пользовательского интерфейса является то, что вызывает RestoreDatabaseButton_Click (так как это взаимодействие с пользовательским интерфейсом).

Прежде чем вы представили новый рабочий поток, эта функция восстанавливала базу данных из потока пользовательского интерфейса. Таким образом, в то время как это происходило, поток пользовательского интерфейса не был доступен для выполнения других своих обязанностей, таких как перерисовка экрана или ответ на другой пользовательский ввод, поэтому ваша заявка замерзала.

Трудности при отладке могут быть связаны только с тем, что восстановление базы данных является потенциально довольно интенсивной операцией, связанной с записью в файловую систему, которая является задачей довольно низкого уровня. Операционная система может решить, что ей нужно уделять большую часть времени восстановлению базы данных, а не «контекстному переключению» очень часто, то есть когда процессор переключится на работу с потоком пользовательского интерфейса. Вы можете попробовать запустить поток, указав более низкий приоритет потока, который должен указывать ОС на более частое обращение к вашему приложению.

Кроме того, вам нужно быть осторожным с тем, как вы управляете рабочей нитью. Например, вы должны, вероятно, сделать его переменной-членом и не позволять пользователю одновременно запускать несколько рабочих потоков (и отключать кнопку восстановления при восстановлении). Вы также должны подумать о том, чтобы обеспечить механизм для остановки потока, если что-то пойдет не так, или когда ваше приложение отключится, чтобы убедиться, что он отключен.

1

Я думаю, что вы хотите сделать здесь, это отправить сообщение в поток пользовательского интерфейса, чтобы принять какое-то действие. Вы можете передать это действие, делегируя поток пользовательского интерфейса синхронно и асинхронно.

В принципе, если поток неинтерфейса вытащить ссылку на некоторый объект пользовательского интерфейса (любой существующий экземпляр класса Control сделает, возможно, экземпляр RestoreDatabaseButton) и вызовет на нем метод Invoke, передав делегат, содержащий код, который должен быть выполнен как параметр. Это приведет к появлению сообщения в потоке пользовательского интерфейса для выполнения делегата в его контексте, а не в контексте вашего потока, отличного от UI. В потоке пользовательского интерфейса уже может быть стек сообщений для обработки, кликов, перемещения мыши, изменения размера и т. Д. И будет обрабатывать каждый в том порядке, в котором он был получен. Метод Invoke не будет возвращен до тех пор, пока не будет выполнен делегированный делегат.

Существует также метод BeginInvoke, который позволяет отправлять сообщение в поток пользовательского интерфейса, которое будет выполняться асинхронно. Этот метод немедленно вернется, позволяя продолжить поток не-UI, хотя делегат, возможно, еще не был выполнен. Метод EndInvoke можно вызывать после метода BeginInvoke и разрешает блокировку потока, отличного от UI, до тех пор, пока делегат не будет выполнен, чтобы он мог получать результаты из выполнения делегата. Если делегат ничего не возвращает, нет необходимости вызывать этот метод.

В любом из этих способов ваш поток, не относящийся к пользовательскому интерфейсу, может влиять на поток пользовательского интерфейса и предпринимать действия против объектов, находящихся на нем.

2

Трудно понять, что именно вы спрашиваете, но я просто хотел рассказать вам об одной небольшой полезной вещи при многопоточных приложениях для Windows.Чтобы обновить любые элементы пользовательского интерфейса из другого потока, вам нужно вызвать invoke. Пример:

Допустим, вы хотите обновить прогресс бар от рабочего потока:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Thread thread = new Thread(DoSomeLongRunningTask); 
     thread.Start(); 
    } 

    private void UpdateProgressBar(int amount) 
    { 
     Action<int> action = (p) => { this.progressBar1.Increment(p); }; 
     this.Invoke(action,amount); 
    } 

    private void DoSomeLongRunningTask() 
    { 
     //do whatever.... 
     UpdateProgressBar(1); 
    } 
} 

EDIT: Мой плохо, я не понимаю, вы не работали с C# 3.0. Вот тот же код без фантазии анонимного метода:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Thread thread = new Thread(DoSomeLongRunningTask); 
     thread.Start(); 
    } 

    delegate void ProgressBarIncrementer(int amount); 

    private void UpdateProgressBarDelegateMethod(int amount) 
    { 
     this.progressBar1.Increment(amount); 
    } 

    private void UpdateProgressBar(int amount) 
    { 
     ProgressBarIncrementer incrementer = UpdateProgressBarDelegateMethod; 
     this.Invoke(incrementer, amount); 
    } 

    private void DoSomeLongRunningTask() 
    { 
     //do whatever.... 
     UpdateProgressBar(1); 
    } 
} 
+0

+1 для использования анонимной функции для обновления пользовательского интерфейса. Это kewl. – Steve

+0

@BFree - Спасибо, но то, что вы предложили, не работает в .NET 2.0. Вместо этого я сделал что-то подобное. Invoke ((MethodInvoker) { LogViewer.Text + = "Выполнение скриптов управления версиями ..." + Environment.NewLine; }); и это сработало хорошо. Есть ли лучший способ в .NET 2.0 для этого? – nickytonline

+0

См. Мое редактирование. Вам нужно создать делегат, а затем передать его методу Invoke. – BFree

1

UI нить отвечает за покраску графических элементов приложения на экране, а также обработки пользовательского ввода.

При разработке многопоточных приложений для Windows, есть на самом деле один основное правило:

Любой код, который получает доступ к UI должен работать в UI потоке.

Это означает, что, например, для того, чтобы обновить статус индикатора выполнения от фонового потока необходимо запустить код, обращающийся объект управления ProgressBar в потоке пользовательского интерфейса.

Существует несколько способов достижения этого. При использовании Windows Forms вы можете использовать класс BackgroundWorker.

Этот класс позволяет запускать код в фоновом потоке и получать доступ к пользовательскому интерфейсу каждый раз, когда вам нужно обновить его со статусом операции, просто , обработав определенные события. Затем BackgroundWorker позаботится о том, чтобы запустить код в соответствующем потоке для вас за кулисами.

1

В нескольких ваших методах вы создаете и запускаете процесс, а затем переходите в цикл While() с вызовом Thread.Sleep (3000) и ожидаете, что свойство Process HasExited вернет true.

Лучший способ сделать это - добавить обработчик для события «Выход» и выполнить код очистки. Поскольку ваши методы работают в потоке пользовательского интерфейса, контуры While() являются частью вашей проблемы.

0

Вот как я бы подойти к этому:

  1. Выполнить все длинные ходовые функции в отдельных нитей. Вы можете выполнить это с помощью класса Thread или BackgroundWorker. В любое время, когда я вижу вызов сна, я бы выбрал новую тему.

  2. Выполнение проверки параметров в потоке пользовательского интерфейса. Вы обновляете текстовые поля LogViewer и возвращаетесь к ошибке. Хорошо.

  3. Создайте новую тему непосредственно после проверки параметров, поток вызовет функцию, которая обрабатывает нереста и ждет процесс.

  4. Сообщить о обратной связи, вызвав Invoke для функции, которая обновляет пользовательский интерфейс. См. Сообщение BFree о том, как вызвать вызов Invoke.

 Смежные вопросы

  • Нет связанных вопросов^_^