[Mono-osx] [Mono-dev] Is System.Drawing (libgdiplus) thread-safe?

Miguel de Icaza miguel at novell.com
Wed Oct 28 17:09:12 EDT 2009


Hello,

     It is thread safe for instances of objects, but you can not mix  
objects that need to interact with the windowing system with calls  
made in separate threads.

     The simple solution is to make sure that anything that interacts  
with the GUI uses Control.Invoke.

On Oct 28, 2009, at 1:37 PM, Alex Shulgin wrote:

> 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
> /* 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));
> 		}
> 	}
> }
> /* 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 <cworth at cworth.org>
> */
>
> #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;
> }
> /* 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;
> }
> _______________________________________________
> Mono-devel-list mailing list
> Mono-devel-list at lists.ximian.com
> http://lists.ximian.com/mailman/listinfo/mono-devel-list



More information about the Mono-osx mailing list