Is System.Drawing (libgdiplus) thread-safe?

Alex Shulgin <[email protected]>
Newsgroups gmane.comp.gnome.winforms,gmane.comp.gnome.mono.devel
Message-ID <[email protected]>
Hi all,

Is System.Drawing by any means thread-safe?

Can I create some threads, create a Graphics object in each of them and 
then work with it from within that thread?  Is this supposed to work or 
am I doing something really stupid here?

I ask because I've noticed random crashes in a WinForms app on OS X (but 
it happens on Linux too).  Most of the time it crashes in 
System.Drawing.Graphics' DrawString or MeasureString methods and seems 
to occur then a background worker thread is working in parallel with UI 
thread.

I've tried to do a stress-test of System.Drawing in a sample 
multi-threaded program.  See attached `test-multi-threaded-drawing.cs'.

On my Linux box it crashes all the time.  I get a wide variety of errors 
from gdb stacktraces with SIGSEGV or SIGABRT in the end, to SIGILL with 
.Net stack trace.

Uncommenting these lock {} lines in the ThreadProc helps, but not an 
option for my real app, as there's simply no single place a lock could 
be added.

I've also tried writing some code in plain C which links to libgdiplus 
directly: see `threads-gdiplus.c'.  It happily crashes just like the C# 
version.

My tests show that even using unsynchronized GdipGetImageGraphicsContext 
/ GdipDeleteGraphics (no fonts stuff touched) can easily lead to crashes.

 From what I've seen, cairo seems to be thread-safe: see attached[1] 
`cairo-multi-thread-text.c'.

Also, there's a few locking used around thread-unsafe fontconfig calls 
in libgdiplus itself.  I didn't examined the whole code, so there's 
possibly other places in it missing locking primitives.

I would appreciate any help on this subject!

--
Regards,
Alex
[1] originally found in the cairo bugzilla for a few-years-old bug; my 
sligthly enhanced version

_______________________________________________
Mono-winforms-list maillist  -  [email protected]
http://lists.ximian.com/mailman/listinfo/mono-winforms-list
test-multi-threaded-drawing.cs (text/x-csharp, 2.2 KB)
/* gmcs test-multi-threaded-drawing.cs -r:System.Drawing,System.Windows.Forms */
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace test {
	public class MainForm : Form {
		private static int threadCount = 0;

		private object consoleLock = new Object();
		private object hwndLock = new Object();

		public static void Main(string[] args) {
			threadCount = args.Length == 0 ? 6 : int.Parse(args[0]);

			Application.Run(new MainForm());
		}

		protected override void OnLoad(EventArgs e) {
			base.OnLoad(e);

			for (int i = 0; i < threadCount; ++i) {
				Thread t = new Thread(ThreadProc);
				t.Start(i);
			}
		}

		void ThreadProc(object data) {
			int threadnum = (int) data;

			Random rnd = new Random((int) DateTime.Now.Ticks);
			int count = rnd.Next(250, 1000);
			lock (consoleLock) {
				Console.WriteLine("thread{0} start: {1}", threadnum, count);
			}

			for (int i = 0; i < count; ++i) {
				string str = CreateRandomString(rnd);

				using (Graphics g = GetGraphicsForMeasurement()) {
					using (Font font = CreateRandomFont(rnd)) {
						//lock (hwndLock) {
						SizeF sz = g.MeasureString(str, font);

						using (Bitmap bmp = new Bitmap((int) sz.Width, (int) sz.Height)) {
							using (Graphics gfx = Graphics.FromImage(bmp)) {
								using (Brush b = Brushes.Red) {
									gfx.DrawString(str, font, b, new PointF(0f, 0f));
								}
							}
						}
						//}
					}
				}
			}

			lock (consoleLock) {
				Console.WriteLine("thread{0} done", threadnum);
			}
		}

		string CreateRandomString(Random rnd) {
			int len = 1 + rnd.Next(60);
			StringBuilder sb = new StringBuilder(len);
			for (int j = 0; j < len; ++j) {
				int ch = (rnd.Next() & 1) == 1 ? 0x41 : 0x61; // 'A' or 'a'
				sb.Append(Char.ConvertFromUtf32(ch + rnd.Next(26)));
			}
			return sb.ToString();
		}

		Graphics GetGraphicsForMeasurement() {
#if NO_GRAPHICS_FROM_HWND
			Bitmap tmp = new Bitmap(1, 1);
			return Graphics.FromImage(tmp);
#else
			Graphics g;
			lock (hwndLock) {
				g = Graphics.FromHwnd(this.Handle);
			}
			return g;
#endif
		}

		Font CreateRandomFont(Random rnd) {
			return new Font("Sans", (int) (8 + rnd.NextDouble()*10));
		}
	}
}
cairo-multi-thread-text.c (text/x-csrc, 2.6 KB)
/* gcc `pkg-config --cflags --libs cairo` -lpthread multi-thread-text.c -o multi-thread-text */

/*
 * Copyright © 2005 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Red Hat, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. Red Hat, Inc. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Carl D. Worth <[email protected]>
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <cairo.h>
#include <pthread.h>

static void *
start (void *closure)
{
    int i;

    for (i = 0; i < 1000; ++i) {
      cairo_surface_t *surface;
      cairo_t *cr;

      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
      cr = cairo_create (surface);

      cairo_move_to (cr, 10, 10);

      cairo_set_font_size (cr, 10);
      cairo_show_text (cr, "Hello world.\n");

      cairo_set_font_size (cr, 9);
      cairo_show_text (cr, "Hello world.\n");

      cairo_set_font_size (cr, 8);
      cairo_show_text (cr, "Hello world.\n");

      cairo_destroy (cr);
      cairo_surface_destroy (surface);
    }

    return NULL;
}

int
main (int argc, char *argv[0])
{
    int i, num_threads;
    pthread_t *pthread;

    if (argc > 1) {
	num_threads = atoi (argv[1]);
    } else {
	num_threads = 6;
	printf ("Running with default value of %d threads.\n"
		"To change, call: %s <number_of_threads>\n",
		num_threads, argv[0]);
    }

    pthread = malloc (num_threads * sizeof (pthread_t));
    assert (pthread != NULL);

    for (i = 0; i < num_threads; i++)
	pthread_create (&pthread[i], NULL, start, NULL);

    for (i = 0; i < num_threads; i++) {
	pthread_join (pthread[i], NULL);
	printf("joined thread%d\n", i);
    }

    return 0;
}
threads-gdiplus.c (text/x-csrc, 2.9 KB)
/* gcc threads-gdiplus.c `pkg-config --cflags glib-2.0` -Wall -lgdiplus -lpthread */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <gdiplus/GdiPlusFlat.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void
fatal(char const* func, GpStatus err)
{
  fprintf(stderr, "%s: %d\n", func, err);
  exit(1);
}

static void*
start(void* closure)
{
  GpStatus err;

  GpBitmap* img;
  int width = 128;
  int height = 32;
  int stride = width*4;

  GpGraphics* gfx;

  GpFontFamily* family;
  GpFont* font;

  WCHAR const str[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','!'};
  size_t len = sizeof(str)/sizeof(WCHAR);
  RectF rect;
  RectF bounds;

  int i;

  for (i = 0; i < 1000; ++i) {

    err = GdipCreateBitmapFromScan0(width, height, stride, PixelFormat32bppARGB, /* scan0 = */ NULL, &img);
    if (err != Ok)
      fatal("GdipCreateBitmapFromScan0", err);

    //assert(pthread_mutex_lock(&mutex) == 0);

    err = GdipGetImageGraphicsContext(img, &gfx);
    if (err != Ok)
      fatal("GdipGetImageGraphicsContext", err);

    //assert(pthread_mutex_unlock(&mutex) == 0);


    err = GdipGetGenericFontFamilySansSerif(&family);
    if (err != Ok)
      fatal("GdipGetGenericFontFamilySansSerif", err);

    err = GdipCreateFont(family, 12.0, FontStyleRegular, UnitPoint, &font);
    if (err != Ok)
      fatal("GdipCreateFont", err);



    rect.X = 0;
    rect.Y = 0;
    rect.Width = width;
    rect.Height = height;

    //assert(pthread_mutex_lock(&mutex) == 0);

    err = GdipMeasureString(gfx, str, len, font, &rect, /* format = */ NULL, &bounds,
                            /* codepoints = */ NULL, /* lines = */ NULL);
    if (err != Ok)
      fatal("GdipMeasureString", err);

    //assert(pthread_mutex_unlock(&mutex) == 0);


    err = GdipDeleteFont(font);
    if (err != Ok)
      fatal("GdipDeleteFont", err);



    err = GdipDeleteGraphics(gfx);
    if (err != Ok)
      fatal("GdipDeleteGraphics", err);


    err = GdipDisposeImage(img);
    if (err != Ok)
      fatal("GdipDisposeImage", err);

  }

  return NULL;
}

int
main(int argc, char *argv[0])
{
  int i, num_threads;
  pthread_t* pthread;

  GpStatus err;
  ULONG_PTR gdiptok;
  GdiplusStartupInput gdipinput;
  GdiplusStartupOutput gdipoutput;

  err = GdiplusStartup(&gdiptok, &gdipinput, &gdipoutput);
  if (err != Ok)
    fatal("GdiplusStartup", err);


  if (argc > 1) {
    num_threads = atoi(argv[1]);
  } else {
    num_threads = 6;
    printf("Running with default value of %d threads.\n"
           "To change, call: %s <number_of_threads>\n",
           num_threads, argv[0]);
  }

  pthread = malloc(num_threads * sizeof(pthread_t));
  assert(pthread != NULL);

  for (i = 0; i < num_threads; i++)
    pthread_create(&pthread[i], NULL, start, NULL);

  for (i = 0; i < num_threads; i++) {
    pthread_join(pthread[i], NULL);
    printf("joined thread%d\n", i);
  }

  pthread_mutex_destroy(&mutex);

  return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.