Mastering Go’s Event-Driven Brilliance: A Journey to Easy Asynchronous Excellence

Photo by Pixabay: https://www.pexels.com/photo/london-new-york-tokyo-and-moscow-clocks-48770/

Introduction

Sometimes, when your program has a task that takes a lot of time, like working with databases, web services, or complex calculations, you might want to let it happen in the background. This way, your program can keep running smoothly without waiting for the time-consuming task to finish. In Go, we can achieve this using goroutines and channels. This article explores a straightforward example involving a virtual windowing system.

Implementation

Let’s assume for the sake of simplicity that resizing windows in our hypothetical system is time-consuming. So we want to do that in a different thread.

We will start by defining a ResizeEvent:

type ResizeEvent struct {
	width  int
	height int
}

Such an events needs a handler, with the following signature:

type ResizeEventHandler func(event ResizeEvent) (int, int, error)

What is returned is the following:

  • The new width
  • The new height
  • If something has gone wrong, you are trying to resize a closed window for example, you can get an error

Now we need a way to listen to the events:

type ResizeEventListener struct {
	events  chan ResizeEvent
	handler ResizeEventHandler
}

func CreateResizeEventListener(handler ResizeEventHandler) ResizeEventListener {
	return ResizeEventListener{make(chan ResizeEvent), handler}
}

func (l *ResizeEventListener) Start(w *Window) {
	go func() {
		for event := range l.events {
			new_width, new_height, error := l.handler(event)
			if error != nil {
				break
			}
			w.width = new_width
			w.height = new_height
		}
	}()
}

func (l *ResizeEventListener) Stop() {
	close(l.events)
}

func (l *ResizeEventListener) Send(event ResizeEvent) {
	l.events <- event
}

Some notes:

  • In ResizeEventListener we define a channel which can receive events, and a function to handle the events
  • The Start function gets a Window struct, which we will define later, as a parameter. This is the window we want to ro resize.
  • In the Stop() method we close the channel, so no more events can be received.
  • And we use the Send() method to send resize events to the window.

Now we come to the Window struct itself:

type Window struct {
	listener ResizeEventListener
	title    string
	width    int
	height   int
}

func CreateWindow(title string, width int, height int, handler ResizeEventHandler) Window {
	return Window{CreateResizeEventListener(handler), title, width, height}
}

func (w *Window) Open() {
	w.listener.Start(w)
	fmt.Printf("Window %s opened with size %dx%d\n", w.title, w.width, w.height)
}

func (w *Window) Close() {
	w.listener.Stop()
	fmt.Printf("Window %s closed\n", w.title)
}

func (w *Window) Resize(event ResizeEvent) {
	w.listener.Send(event)
}

This is pretty self-explanatory, but a few points:

  1. We add a listener field to the struct, so we can listen to resize events.
  2. In the Open() method we start listening to resizing events
  3. When we call the Close() method, we stop listening to resizing events, since resizing a closed window makes no sense.
  4. The Resize() method is used to send resizing events to the window.

Testing time

Time to test our setup:

func main() {
	window := CreateWindow("My Window", 800, 600, func(event ResizeEvent) (int, int, error) {
		fmt.Printf("Window resized to %dx%d\n", event.width, event.height)
		return event.width, event.height, nil
	})
	window.Open()
	window.Resize(ResizeEvent{1024, 768})
	window.Resize(ResizeEvent{644, 484})
	window.Resize(ResizeEvent{800, 600})
	window.Close()
	fmt.Printf("Height is %d\n", window.height)
	fmt.Printf("Width is %d\n", window.width)

}

What we do here is create a window, with an initial width, height, title and a resizing handler. Next we open,resize the window three times, and close it, after which we print out the width and height of the resulting window.

Conclusion

Implementing a multi-threaded event handler using channels and goroutines in Go is surprisingly simple. While this article focuses on a specific example, there’s room for enhancements, such as building a more generic event handler, which could be a topic for a future article.

Leave a Reply

Your email address will not be published. Required fields are marked *