GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — Plotly.NET/src/Plotly.NET.ImageExport/AsyncHelper.fs at dev · plotly/Plotly.NET Expand file tree/ Copy pathAsyncHelper.fsMore file actions 78 lines (58 loc) · 2.71 KB / Copy pathAsyncHelper.fsFile metadata and controls78 lines (58 loc) · 2.71 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 module Plotly.NET.ImageExport.AsyncHelper open System.Threading open System.Threading.Tasks (* This is a workaround to avoid deadlocks https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d TL;DR in many cases, for example, GUI apps, SynchronizationContext is overriden to *post* the executing code on the initial (UI) thread. For example, consider this code public async Task OnClick1() { var chart = ...; var base64 = ImageExport.toBase64PNGStringAsync()(chart).Result; myButton.Text = base64; } Here we have an async method. Normally you should use await and not use .Result, but assume for some reason the sync version is used. What happens under the hood is, public async Task OnClick1() { var chart = ...; var task = ImageExport.toBase64PNGStringAsync()(chart); task.ContinueWith(() => UIThread.Schedule(() => myButton.Text = Result; ) ); task.Wait(); } (this is pseudo-code) So basically, we set the task to wait until it finishes. However, part of it being finished is to actually execute the code with button.Text = .... The waiting happens on the UI thread, exactly on the same thread as where we're waiting for it to do another job! That's not the only place we potentially deadlock by using fake synchronous functions. The reason why it happens, is because frameworks (or actually anyone) override SynchronizationContext. In GUI and game development it's very useful to keep UI logic on one thread. But our rendering does not ever callback to it, we're independent of where the logic actually happens. That's why what we do is we set the synchronization context to null, do the job, and then restore it. It is a workaround, because it doesn't have to work everywhere and independently. But it will work for most cases. When will it also break? For example, if we decide to take in some callback as a para- meter, which potentially accesses the UI thread (or whatever). In Unity, for instance, you can only access Unity API from the main thread. So our fake synchronous function will crash in the end, because due to the overriden (by us) sync context, the callback will be executed in some random thread (as opposed to being posted back to the UI one). However, our solution should work in most cases. Credit to [@DaZombieKiller](https://github.com/DaZombieKiller) for helping. *) let runSync job input = let current = SynchronizationContext.Current SynchronizationContext.SetSynchronizationContext null try job input finally SynchronizationContext.SetSynchronizationContext current let taskSync (task: Task<'a>) = task |> runSync (fun t -> t.Result) let taskSyncUnit (task: Task) = task |> runSync (fun t -> t.Wait()) |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |