How to correctly implement a goroutine in terminal application

huangapple go评论75阅读模式
英文:

How to correctly implement a goroutine in terminal application

问题

我正在尝试在终端中创建一个HTTP请求接口,您可以在其中传递一些数据(URL、响应体等),然后我会发送请求并在某个地方显示数据。

我正在尝试在goroutine中进行请求,并显示通道给出的结果。当我进行快速请求时,这是不可能注意到的,但我创建了一个简单的Node端点来测试一个计算密集型的端点:

app.use(express.json())

app.get("/", (req, res) => {     
    new Promise(resolve => {
        setTimeout(() => resolve(), 3000)
    }).then(() => res.status(200).json({message: "OK"}))
    .catch(err => res.status(500).json({error: err.message}))
})

app.listen(4000, () => console.log("Server up"))

当我调用这个端点时,整个界面会冻结,只有在请求完成后我才能继续使用GUI。例如,我尝试在请求进行时在GUI中包含一个加载框,但冻结发生了,加载框在请求完成后才显示出来。

这是httpRequest函数的代码:

func httpRequest(url, method string, body []byte, results chan Result) {
    client := &http.Client{}
    req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
    req.Header.Add("content-type", "application/json")
    if err != nil {
        results <- Result{err: err}
    }

    res, err := client.Do(req)
    if err != nil {
        results <- Result{err: err}
    }

    defer res.Body.Close()

    r := Result{
        method: res.Request.Method,
        url:    res.Request.URL.String(),
        path:   res.Request.URL.Path,
        proto:  res.Proto,
        status: res.Status,
        header: res.Header,
    }

    b, err := io.ReadAll(res.Body)
    if err != nil {
        results <- Result{err: err}
    }

    r.body = string(b)
    results <- r
} 

它将在processRequest函数中被调用:

func processRequest(g *ui.Gui, v *ui.View) error {
    method_view, err := g.View("method")
    if err != nil {
        return err
    }
    method_view.Clear()
    fmt.Fprintln(method_view, "loading...")

    // 在这里获取所有请求数据...

    out, err := g.View("res-output")
    if err != nil {
        return err
    }

    r := make(chan Result, 1)
    start := time.Now()

    switch active_method {
    case 0:
        go httpRequest(api_url, "GET", nil, r)
    case 1:
        go httpRequest(api_url, "POST", request_body_data, r)
    case 2:
        go httpRequest(api_url, "DELETE", request_body_data, r)
    case 3:
        go httpRequest(api_url, "PUT", request_body_data, r)
    }

    result := <-r
    if result.err != nil {
        fmt.Fprintf(out, "Error: %s\n", result.err.Error())
        return nil
    }
    fmt.Fprintf(out, "Time: %f s\n", time.Since(start).Seconds())
    fmt.Fprintln(out, formatResponse(result))

    history_view, err := g.View("history")
    if err != nil {
        return err
    }

    history_item := fmt.Sprintf("%s %s %s\n", result.method, result.url, result.status)
    fmt.Fprintln(history_view, history_item)

    close(r)

    method_view.Clear()
    fmt.Fprintln(method_view, methods[active_method]) // 清除顶部的加载并重写请求方法

    return nil
}

我能否实现这样的行为,即在进行请求时仍然可以操作UI?我还尝试创建另一个通道done来通知processRequest函数,但结果相同。

英文:

I'm trying to create a HTTP request interface in the terminal where you can pass some data (url, response body, etc) and then I make the request and display the data somewhere.
I'm trying to do the request in a goroutine, and display the results that the channel gives me. When I'm doing quick requests, this is impossible to note, but I created a simple Node endpoint to test a computationally heavy endpoint:

    app.use(express.json())
    
    app.get(&quot;/&quot;, (req, res) =&gt; {     
         new Promise(resolve =&gt; {
             setTimeout(() =&gt; resolve(), 3000)
         }).then(() =&gt; res.status(200).json({message: &quot;OK&quot;}))
        .catch(err =&gt; res.status(500).json({error: err.message}))
    })
   
    app.listen(4000, () =&gt; console.log(&quot;Server up&quot;))

When I call this endpoint, the whole ui freezes and only after the request is complete I can continue to use the GUI. For example, I tried to include a loading box at the GUI while the requests were being made, but the freeze happened, and the loading got displayed after the request completed.

Here's the code for the httpRequest function

func httpRequest(url, method string, body []byte, results chan Result) {
	client := &amp;http.Client{}
	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
	req.Header.Add(&quot;content-type&quot;, &quot;application/json&quot;)
	if err != nil {
		results &lt;- Result{err: err}
	}

	res, err := client.Do(req)
	if err != nil {
		results &lt;- Result{err: err}
	}

	defer res.Body.Close()

	r := Result{
		method: res.Request.Method,
		url:    res.Request.URL.String(),
		path:   res.Request.URL.Path,
		proto:  res.Proto,
		status: res.Status,
		header: res.Header,
	}

	b, err := io.ReadAll(res.Body)
	if err != nil {
		results &lt;- Result{err: err}
	}

	r.body = string(b)
	results &lt;- r
} 

It is going to be called on the processRequest function:

func processRequest(g *ui.Gui, v *ui.View) error {
	method_view, err := g.View(&quot;method&quot;)
	if err != nil {
	    return err
	 }
	 method_view.Clear()
	 fmt.Fprintln(method_view, &quot;loading...&quot;)

	
     // GET ALL REQUEST DATA HERE...


	out, err := g.View(&quot;res-output&quot;)
	if err != nil {
		return err
	}

	r := make(chan Result, 1)
	start := time.Now()

	switch active_method {
	case 0:
		go httpRequest(api_url, &quot;GET&quot;, nil, r)
	case 1:
		go httpRequest(api_url, &quot;POST&quot;, request_body_data, r)
	case 2:
		go httpRequest(api_url, &quot;DELETE&quot;, request_body_data, r)
	case 3:
		go httpRequest(api_url, &quot;PUT&quot;, request_body_data, r)
	}

	result := &lt;-r
	if result.err != nil {
		fmt.Fprintf(out, &quot;Error: %s\n&quot;, result.err.Error())
		return nil
	}
	fmt.Fprintf(out, &quot;Time: %f s\n&quot;, time.Since(start).Seconds())
	fmt.Fprintln(out, formatResponse(result))

	history_view, err := g.View(&quot;history&quot;)
	if err != nil {
		return err
	}

	history_item := fmt.Sprintf(&quot;%s %s %s\n&quot;, result.method, result.url, result.status)
	fmt.Fprintln(history_view, history_item)

	close(r)

    method_view.Clear()
    fmt.Fprintln(method_view, methods[active_method]) // CLEAR THE LOADING FROM TOP AND REWRITE REQUEST METHOD

	return nil
}

Can I achieve a behaviour where I can still mess up with the UI while the request is being made?
I also tried to create another channel, done, to notify the processRequest func, but I get the same results.

答案1

得分: 1

你当前代码的问题在于,虽然你正在并发地发起请求,但是你在等待结果result := <-r时进行了阻塞。这就抵消了在go协程中运行请求的目的,因为在此期间你没有做任何事情(比如处理UI事件)。

你可以按照一种方式组织你的代码,使得响应也在一个go协程中处理,而不仅仅是请求,这样你的应用程序就可以像正常情况下一样使用,并且在将来某个时间点收到请求的响应时,响应可以并发地更新UI。

换句话说,processRequest应该在一个go协程中运行,而不是httpRequest

英文:

The problem with your current code is that while you're making the request concurrently, you're blocking on waiting for the result result := &lt;-r. This is negating the point of running the request in a go-routine because you're not doing anything (such as handling UI events) in the meantime.

You could structure your code in a way that the response is handled in a go-routine, not just the request, then your application can be used like normal and the response can concurrently update the UI when the request's response is received at some point in the future.

In other words, processRequest should be run in a go-routine instead of httpRequest.

huangapple
  • 本文由 发表于 2021年6月28日 04:19:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/68154959.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定