如何进行C#异步编程?

How to do C# asynchronous programming?

我正在使用我很久以前用 Visual Basic.net 编写的数据库导出工具。

我切换到 C# 并喜欢用 C# 重新编程,因为我积累了比以前更多的经验:-)

我的 C# 应用程序 UI 挂起,因为有一个大的数据库查询要做。所以我了解了异步编程。也尝试过线程和任务,但我无法找到适合我的问题的方法。

这就是我所拥有的:它是一个 Windows 表单应用程序,并且有一个创建新线程的开始按钮。

名为 Export 的方法是我在名为 actions.cs 的第二个 class 文件中创建的非静态方法。它是非静态的,因为该方法应该在代码中经常重复使用,但参数不同。

所以我在Form1的Button_Clicked事件中用相应的参数实例化了方法:

actions KuliMon = new actions()
        {
            ExportPath = settings.ReadActSetting("baexport", "ExportPfad", ""),
            Trennzeichen = settings.ReadGlobSetting("Trennzeichen", ";"),
            ConnectionString = settings.ReadGlobSetting("Mand1_odbc", ""),
            SQLFile = "kuli.sql",
            ExportAktion = "kuli"
        };

然后我从 Button_click 事件开始了这样的话题:

Thread ExportThread = new Thread(KuliMon.Export);
        ExportThread.Start();

这行得通。不粘 GUI。但是现在问题来了。我在 actions.cs 中的导出方法正在将 DB-Query 导出到一个 csv 文件中,但也应该 return 将结果放入一个字符串变量中,然后我可以在 Form1 的文本框中显示它。

通过阅读一些资料,我发现 Invoke-Method 对我有很大帮助。在 Thread.Start() 下,我添加了以下内容:

this.Invoke((MethodInvoker)delegate
        {
             tx_main_t1.Text = "Hello";
        });

当我单击按钮时,文本框显示 "hello"。但是我需要线程中的方法 Export 运行 中的 Return 字符串而不是 hello。问题是如何获取包含查询结果的字符串。

根据我的理解,线程方法必须调用一个 void 方法并且不能 return 一个值。

我想创建一个 public 属性 字符串并在导出中用 return 值填充该字符串,如下所示:

public string results { get; set; }

我试过

而不是在方法导出中使用 return ReturnValue
results = ReturnValue;

然后在 Form1 中,我尝试用 KuliMon.results 填充文本框,但它是空的,因为我已经按照我的想法创建了一个 Export 实例。

您应该查看 BackGroundWorker class
https://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

您可以指定一个函数在工作人员完成工作时调用,并让该函数更新您的 UI。

使用 async await supported in .Net 4.5 and c#5 (you can get support for this in erarlier .Net versions with AsyncBridge package for 3.5 or for 4.0)

private async void button1_Click(object sender, EventArgs e)
{
     button1.Enabled = false;
     try
     {
          //Code will wait here for your method to return without blocking UI Exceptions or Result will be automatically Scheduled to UI thread
          string result = await DoStuffAsync("myParameter");
     }
     catch
     {
          MessageBox.Show("Ups an error");
     }
     finally
     {
         button1.Enabled = true;
     }

}

/// <summary>
/// Wraps the synchron implementation off DoStuff
/// </summary>    
public Task<string> DoStuffAsync(string s)
{
   return Task.Factory.StartNew(DoStuff, s); //here parameter s is forwarded to your synchronus implementation
}

/// <summary>
/// Your orginal synchron implementation with the dbQuerry
/// or long running calculations
/// </summary>   
public string DoStuff(string s)
{
      //do your normal code;
      return result
}

数据库查询和写入文件都是 I/O-bound 操作,因此它们很适合 asyncawait

首先,您将定义 KuliMon.Export 的异步版本:

async Task<string> ExportAsync()
{
  var data = await myQuery.ToListAsync();

  await myStream.WriteAsync(...);

  return results;
}

例如,您可以使用 Entity Framework 6 for asynchronous query support

然后您可以从您的 UI 中这样调用它:

async void button1_Clicked(...)
{
  TextBox1.Text = await myInstance.ExportAsync();
}

如果您出于某种原因不能使用异步数据库查询(例如,我认为 Oracle 目前不支持它们),那么您可以使用调用同步 API 的后台线程。请注意 Task.RunThreadBackgroundWorker 的现代替代品:

string Export();
...
async void button1_Clicked(...)
{
  TextBox1.Text = await Task.Run(() => myInstance.Export());
}