Sign up

GtkD code review - How to update a progressbar using data sharing concurrency

Hello, I need a code review on my strategy of updating a GtkD progressbar. Gtk is not thread safe, I interpret that as "I must only access data available in the main thread from the Gtk objects".

This example is a simplified excerpt of my project. I have never done concurrency before and thus I would like a code review. The goal here is

  1. Downloading a list of file in parallel
  2. Update the gtk progressbar periodically to show the overall download progress.
import gio.Application : GioApplication = Application;
import gtk.Application : Application;
import gtk.ApplicationWindow : ApplicationWindow;
import gtk.ProgressBar : ProgressBar;
import glib.Timeout : Timeout;
import gtkc.gtktypes : GApplicationFlags, GPriority;

class Downloader
{
	string[] links = [`link1`, `link2`, `link3`, `link4`];
	private shared size_t completed = 0;

	double getFraction()
	{
		return cast(double) completed / links.length;
	}

	static void start(ref Downloader downloader)
	{
		import std.parallelism : parallel;
		import core.thread : Thread, seconds;

		{
			// emulate HTTP response overhead;
			Thread.sleep(seconds(2));
		}
		synchronized
		{
			// emulate random Download time
			import std.random : Random, uniform;

			auto rnd = Random(4361);

			foreach (_; downloader.links.parallel())
			{
				Thread.sleep(uniform(0, 6, rnd).seconds());
				++cast() downloader.completed;
			}
		}
	}
}

class ProgressIndicatorBar : ProgressBar
{
	this()
	{
		super.setShowText(true);
		super.setPulseStep(0.2);
	}
}

class PrimaryWindow : ApplicationWindow
{
	const int width = 320, height = 100;
	ProgressIndicatorBar pib;
	this(Application app)
	{
		super(app);
		super.setSizeRequest(width, height);
		scope (success)
			super.showAll();

		pib = new ProgressIndicatorBar();
		scope (success)
			add(pib);

		auto downloader = new Downloader();

		import std.parallelism : task;

		auto downloadTask = task!(Downloader.start)(downloader);
		downloadTask.executeInNewThread();

		auto timeout = new Timeout(100, delegate bool() {
			if (downloader.completed < downloader.links.length)
			{
				if (downloader.completed == 0)
				{
					pib.setText(`Awaiting response...`);
					pib.pulse();
				}
				else
				{
					pib.setText(`Downloading...`);
					pib.setFraction(downloader.getFraction());
				}
				return true;
			}
			else
			{
				super.setTitle(`Downloading complete`);
				// pib.setShowText(false);
				pib.setVisible(false);
				return false;
			}
		}, GPriority.HIGH);
	}
}

int main(string[] args)
{
	auto application = new Application(`org.gitlab.helloprogressbar`, GApplicationFlags.FLAGS_NONE);
	application.addOnActivate(delegate void(GioApplication app) {
		auto appWindow = new PrimaryWindow(application);
	});
	return application.run(args);
}

Original thread: https://forum.dlang.org/post/kqvpjwbkpravywaldiof@forum.dlang.org

Re: GtkD code review - How to update a progressbar using data sharing concurrency

On 20-06-2020 18:34, adnan338 wrote:

Hello, I need a code review on my strategy of updating a GtkD progressbar. Gtk is not thread safe, I interpret that as "I must only access data available in the main thread from the Gtk objects".

This example is a simplified excerpt of my project. I have never done concurrency before and thus I would like a code review. The goal here is

  1. Downloading a list of file in parallel
  2. Update the gtk progressbar periodically to show the overall download progress.
......

Original thread: https://forum.dlang.org/post/kqvpjwbkpravywaldiof@forum.dlang.org

Using glib.Timeout or glib.Idle is probably the best approach to update
the main thread/GUI.

If you are worried about a data race you could use, std.concurrency.send
and std.concurrency.receiveTimeout.