在单独的线程中调用方法

4
我需要在单独的线程中调用类方法。该方法有一个参数和一个返回值。
详细信息:
我有一个带有两个文本框的表单。用户在第一个文本框中输入值,并在第二个文本框中获得结果:
 private void tbWord_TextChanged(object sender, TextChangedEventArgs e)
 {
     tbResponce.Text = Wiki.DoWork(tbWord.Text);
 }

Wiki级别必须使用维基百科API:
public class Wiki
{
    private class State
    {
        public EventWaitHandle eventWaitHandle = new ManualResetEvent(false);
        public String result;
        public String word;
    }

    private static void PerformUserWorkItem( Object stateObject )
    {
        State state = stateObject as State;

        if(state != null)
        {
            Uri address = new Uri("http://en.wikipedia.org/w/api.php?action=opensearch&search=" + state.word);
            HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
            HttpWebResponse responce = (HttpWebResponse)request.GetResponse();

            StreamReader myStreamReader = new StreamReader(responce.GetResponseStream(), Encoding.GetEncoding(1251));
            state.result = myStreamReader.ReadToEnd();

            state.eventWaitHandle.Set(); 
        }
    }

    public static String DoWork(String _word)
    {
        State state = new State();
        state.word = _word;

        ThreadPool.QueueUserWorkItem(PerformUserWorkItem, state);
        state.eventWaitHandle.WaitOne();
        return state.result;                   
    }     
}

问题

当用户在tbWord中按键时,主窗体会冻结并等待Wiki类完成所有工作。

我该如何异步运行DoWork?

6个回答

11

或者像这样使用TPL:

private void tbWord_TextChanged(object sender, TextChangedEventArgs e) 
 { 
    Task.Factory.StartNew(() =>
    {
        return Wiki.DoWrok(tbWord.Text);
    }).ContinueWith(taskState =>
    {
        tbResponce.Text = taskState.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
 } 

参见:http://msdn.microsoft.com/en-us/library/dd997394.aspx


如果您可以使用 .Net 4,那么您应该绝对使用 TPL (System.Threading.Tasks)。这也将使您能够跟踪任何未完成的任务并更轻松地取消它们。例如,用户在文本框中输入 "abcd",然后触发了一个到维基百科的任务。在此完成之前,用户输入了 "efgh"。如果 "efgh" 查找完成,则它将填充结果,并且这些结果将被 "abcd" 搜索覆盖。 - Ragoczy

2

考虑使用BackgroundWorker类。这将允许您异步运行操作。当BackgroundWorker通知您已完成操作时,您可以在Dispatcher线程上更新UI。


为什么我不能在我的WPF应用程序中将BackgroundWorker放在表单上(http://imagebin.org/199796)? - ceth
看了你的图片,BackgroundWorker 组件被列为 WinForms 组件。如果你在使用 WPF,可能需要在某个地方添加 WinForms 主机。或者,可以直接在代码后台创建它。参见这里获取示例。 - Samuel Slade

1

0

您可以使用 WPF Dispatcher 在主 UI 线程中执行操作,因为您已经在 DoWork 中使用了 WaitOne() - 主 DoWork 操作将是同步的,因此您可以使用 Dispatcher.BeginInvoke() 异步地推送 UI 线程中的更改:

Dispatcher.CurrentDispatcher.BeginInvoke(
        DispatcherPriority.Input, 
        new ThreadStart(() => 
          { 
             tbResponce.Text = Wiki.DoWork(tbWord.Text); 
          }));

请告诉我这是否适合您,因为Polity提到了可能存在的同步问题


0

不要在UI线程中等待ThreadPool线程完成,而是订阅完成事件:

class Program
{
    static void Main(string[] args)
    {
        var program = new Program();
        program.DoWorkComplete += (s, e) => Console.WriteLine(e.Result);
        program.DoWorkAsync();

        // Wait 10 seconds, worker thread should finish beforehand, so
        // you see console output
        Thread.Sleep(10000);
    }

    public event EventHandler<CompleteEventArgs> DoWorkComplete;

    public void DoWorkAsync()
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            // Download data here
            string result = "Result from Wiki";

            if(DoWorkComplete != null)
                DoWorkComplete(this, new CompleteEventArgs(result));
        });
    }

    public class CompleteEventArgs : EventArgs
    {
        public CompleteEventArgs(string result)
        {
            this.Result = result;
        }

        public string Result { get; private set; }
    }
}

编辑:这是一个控制台应用程序。我可以从任何线程中使用WriteLine。如果您正在使用WPF,则需要使用Dispatcher确保您在UI线程中处理DoWorkComplete事件(sll already stated)。如果您正在使用Windows Forms,则必须使用Control.Invoke执行相同的操作。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接