我假设您担心分配不必要的对象。我认为这种担忧是值得称赞的。
您可能追求的是一个位图 API,其中通过提供 PNG 图像的字节,Stream<byte>
然后位图 API 在套接字需要时生成。
System.Drawing
似乎不支持这种行为,可能支持WIC
(.NET 包装器通过优秀的SharpDX
库存在)。
但是,这意味着在传输期间保持潜在的昂贵对象(位图、画笔等)处于活动状态。字节数组可能是存储结果的更有效方式。
为了避免不必要地分配对象的另一种方法是缓存它们。由于System.Drawing
对象是可变的并且从多个线程中使用不安全,因此问题变得更加严重。但是,您可以使用ThreadLocal
.
在下面的示例代码中,大多数对象都是按Thread
. 每次调用创建的唯一对象draw
是返回的字节数组,但这可能是 PNG 数据的有效存储(调用可能System.Drawing
分配对象,但我们无法控制)。由于我没有找到一种方法来监听线程的“死亡”,这意味着dispose
当线程不再需要对象时,使用该方法手动处理对象很重要。
希望这很有趣
open System
open System.Drawing
open System.Drawing.Imaging
open System.IO
open System.Threading
module BitmapCreator =
module internal Details =
let dispose (d : IDisposable) =
if d <> null then
try
d.Dispose ()
with
| e -> () // TODO: log
// state is ThreadLocal, it means the resources gets initialized once per thread
let state =
let initializer () =
// Allocate all objects needed for work
let font = new Font("Courier", 24.0F)
let red = new SolidBrush(Color.Red)
let white = new SolidBrush(Color.White)
let bitmap = new Bitmap(600,400)
let g = Graphics.FromImage bitmap
let ms = new MemoryStream 1024
// disposer should be called when Thread is terminating to reclaim
// resources as fast as possible
let disposer () =
dispose ms
dispose g
dispose bitmap
dispose white
dispose red
dispose font
font, red, white, bitmap, g, ms, disposer
new ThreadLocal<_>(initializer)
// Draws text on a bitmap and returns that as a byte array
let draw text =
// Grab the state for the current thread
let font, red, white, bitmap, g, ms, _ = Details.state.Value
g.FillRectangle(white, 0.0F, 0.0F, 600.0F, 400.0F)
g.DrawString(text, font, red, 10.0F, 40.0F)
g.Flush()
// Resets the memory stream
// The capacity is preserved meaning as long as the generated
// images is equal or smaller in size no realloc is needed
ms.Seek (0L, SeekOrigin.Begin) |> ignore
ms.SetLength 0L
bitmap.Save(ms, ImageFormat.Png)
// Here a new array is allocated per call
// Depending on how FillRectangle/DrawString works this is hopefully our
// only allocation
ms.ToArray()
// Disposes all BitmapCreator resources held by the current thread
let dispose () =
let _, _, _, _, _, _, disposer = Details.state.Value
disposer ()
[<EntryPoint>]
let main argv =
// Saves some bitmaps to file, the name include the thread pid in order
// to not overwrite other threads' images
let save () =
let texts = [|"Hello"; "There"|]
let tid = Thread.CurrentThread.ManagedThreadId
for text in texts do
File.WriteAllBytes (sprintf "%s_%d.png" text tid, BitmapCreator.draw text)
// Runs a in other thread, disposes BitmapCreator resources when done
let runInOtherThread (a : unit -> unit) =
let a () =
try
a ()
finally
BitmapCreator.dispose ()
let thread = Thread a
thread.Start ()
thread.Join ()
Environment.CurrentDirectory <- AppDomain.CurrentDomain.BaseDirectory
try
save () // Here we allocate BitmapCreator resources
save () // Since the same thread is calling the resources will reused
runInOtherThread save // New thread, new resources
runInOtherThread save // New thread, new resources
finally
BitmapCreator.dispose ()
0