Skip to content

[BUG] SKCanvas owned by SKSurface gets disposed #1343

@mclose90

Description

@mclose90

Description

The SKCanvas object that is originally owned by the SKSurface ends up being disposed at some point in a highly multi-threaded environment. We have seen this happen with both surfaces provided by a SKElement as well as surfaces created off screen. The issue may appear immediately or after a couple mins of running the demo. The more threads the more likely it is to happen. The debugger shows that the SKSurface still holds a canvas object but looking through source I suspect this is a newly created canvas different from the original one.

Code

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:skia ="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <skia:SKElement
            Name="Canvas"
            PaintSurface="SKElement_PaintSurface" />
    </Grid>
</Window>
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        const int NUM_THREADS = 16;
        SKImage _image = null;
        object _imageLock = new object();
        Random random = new Random();



        public MainWindow()
        {
            InitializeComponent();
            
            for (int i = 0; i < NUM_THREADS; i++)
                Task.Run(() =>
                {
                    for (; ; )
                        UpdateImage();
                });
        }

        private void UpdateImage()
        {
            using (var surface = SKSurface.Create(new SKImageInfo((int)ActualWidth, (int)ActualHeight, SKImageInfo.PlatformColorType, SKAlphaType.Premul)))
            {
                if (surface != null)
                {
                    var canvas = surface.Canvas;

                    canvas.Clear(SKColors.Orange);
                    using (var paint = new SKPaint() { TextSize = 136, TextAlign = SKTextAlign.Center })
                        canvas.DrawText($"{random.Next()}", new SKPoint((float)ActualWidth / 2, (float)ActualHeight / 2), paint);
                    canvas.RestoreToCount(-1);

                    var image = surface.Snapshot();

                    lock (_imageLock)
                        _image = image;
                }
            }
            Application.Current?.Dispatcher?.Invoke(() => Canvas?.InvalidateVisual());
        }

        private void SKElement_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e)
        {
            lock (_imageLock)
            {
                if (_image != null)
                    e.Surface.Canvas.DrawImage(_image, new SKRect(0, 0, _image.Width, _image.Height), new SKRect(0, 0, e.Info.Width, e.Info.Height));
            }
        }
    }
}

Expected Behavior

The canvas should live until the surface is disposed.

Actual Behavior

The canvas is being disposed mid render sequence.

Basic Information

  • Version with issue: 1.68.3 , 2.80.0 preview 14
  • Last known good version: N/A
  • IDE: VS 2019 Pro
  • Platform Target Frameworks: .Net Standard 2.0 , .Net Framework 4.7.2, WPF, Windows 10 update 1809

Screenshots

image

image

Reproduction Link
See source code above. That encompasses the entire program to force this to happen. The more threads the more it becomes visible. May take a few seconds to a few mins of the code running to recreate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions