Hey RayV

Excellent example! It explains how to subscribe to C#-based events very well.

My only suggestion (I've just started learning F# but have been doing C#/winforms for awhile now) is that it may be a cleaner implementation if you use the composition pattern instead of inheritance. Because you've subclassed BackgroundWorker, there are a lot of properties and methods available to the user that they really don't need to have. For example, the user could manually set WorkerReportsProgress=false and then wonder what happened :)

The way to get around that would be for your Worker class to have a reference to its own BackgroundWorker, subscribe to the BackgroundWorker's events (only the ones that it needs) and then fire its own events for any F# users to hook into.

Here's an example. It's a little more code, but also doubles as a good reference on how to fire F#-based events (I had a lot of trouble finding a good example for this online).

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#light


  

open System

open System.Drawing

open System.ComponentModel

open System.Windows.Forms


  


  

type Worker() as this = class

  let _worker = new BackgroundWorker()  

  let progressChangedFire,progressChangedEvent = IEvent.create()

  let workerCompletedFire,workerCompletedEvent = IEvent.create()


    

  let doWorkCallback (args : DoWorkEventArgs) =

    let mutable timesThrough = 0

    while timesThrough < 10 do

      Threading.Thread.Sleep(500)

      timesThrough <- timesThrough + 1

      _worker.ReportProgress(timesThrough)


        

  let progressChangedCallback (args : ProgressChangedEventArgs) = 

    progressChangedFire (this,args)

<br / />      

  let workerCompletedCallback (args : RunWorkerCompletedEventArgs) = 

    workerCompletedFire (this,args)


    

  do _worker.WorkerReportsProgress <- true

  do _worker.DoWork.Add(doWorkCallback)

  do _worker.ProgressChanged.Add(progressChangedCallback)

  do _worker.RunWorkerCompleted.Add(workerCompletedCallback)


    

  member this.Start() =

    _worker.RunWorkerAsync()


      

  member this.ProgressChanged = 

    progressChangedEvent


      

  member this.WorkerCompleted = 

    workerCompletedEvent


  

end


  

// Uncomment this part to test with FSI without WinForms

(*let printProgress (sender,args : ProgressChangedEventArgs) = 

  printfn "Progress Percentage = %d" args.ProgressPercentage


  

let workerDone (sender,args : RunWorkerCompletedEventArgs) = 

  printfn "Done!"


  

let a = new Worker()

a.ProgressChanged.Add(printProgress)

a.WorkerCompleted.Add(workerDone)

a.Start()*)


  


  

type WorkerForm(worker : Worker) as this =

  inherit Form() 


    

  let _btnWork = new Button() :> Control

  let _txtTimes = new TextBox() :> Control


    

  let _btnWork_Click args =

    worker.Start()


      

  let worker_ProgressChanged (sender, args : ProgressChangedEventArgs) =

    _txtTimes.Text <- sprintf "# of times %i" args.ProgressPercentage


      

  let worker_Completed (sender, args : RunWorkerCompletedEventArgs) =

    _txtTimes.Text <- "Done!"


    

  do this.Text <- "BackgroundWorker How-To"

  do this.Size <- new Size(200,200)

  do this.FormBorderStyle <- FormBorderStyle.FixedSingle


    

  do _btnWork.Text <- "Start Work"

  do _btnWork.Location <- new Point(10,10)

  do _btnWork.Click.Add(_btnWork_Click)

  do this.Controls.Add(_btnWork)


    

  do _txtTimes.Text <- "0"

  do _txtTimes.Location <- new Point(10,50)

  do this.Controls.Add(_txtTimes)


    

  do worker.ProgressChanged.Add(worker_ProgressChanged)

  do worker.WorkerCompleted.Add(worker_Completed)


  

let form = new WorkerForm(new Worker())

form.Show()

If you do it this way, when you type in Worker., Intellisense only shows you 3 things:

  1. that you can Start() it
  2. you can hook into the ProgressChanged event
  3. you can hook into the WorkerCompleted event

I also reworked the WinForms code (more for my personal learning than anything else) as an example of "more than 1 way to skin a cat".

Hope that helps someone. This was a very good example to learn events and WinForms interop. :)

-Matt

By on 10/21/2007 10:46 PM ()

Hey, Matt, I'm glad that you enjoyed the example and thanks for the followup!

I can certainly appreciate using composition versus inheritance; your point is well taken indeed. I just wanted to post the quickest example that I could think of.

Of course, now that we have some great explanations of how to use asynchronous workflows I'm not sure how often F#'ers will need the BackGroundWorker if at all.

By on 10/22/2007 5:27 AM ()

I think there's still a place for BackGroundWorker, it depends on the type of concurrency your doing. I think BackGroundWorker is useful for coarse grained concurrency, having the RunWorkerCompleted event it really useful, especially with win forms as I believe the RunWorkerCompleted event is always called on the GUI thread allowing you to update gui controls (though I couldn't find a statement to this effect on MSDN).

I'm currently working on an example that uses both asynchrounous workflows and BackGroundWorker. Will be on the blog in a couple of days.

Also, Don gave me a sneak peak at the concurrency chapter of "Expert F#" and he spends quite a few pages talking about BackGroundWorker before moving on to asynchrounous workflows and other concurrency technologies. All very good stuff.

By on 10/22/2007 7:21 AM ()

I think there's still a place for BackGroundWorker, it depends on the type of concurrency your doing. I think BackGroundWorker is useful for coarse grained concurrency...

Thanks for the reply, Robert. That clears up some questions I had lingering in my own mind. Being new to asynchronous workflows, I was struggling with how to perform coarse-grained, long-running worker processes that would report progress and completedness in the same way that a BackgroundWorker does. I figured I was just missing something but it sounds like I may have been more spot on than I thought.

I'm looking forward to your blog entry on the topic and, of course, to Don's book. [:)]

By on 10/22/2007 9:27 AM ()

One of the things I like about this thread is that "BackgroundWorker" is exactly the example I use in the Expert F# book to illustrate the "composition v. inheritance" point, and especially to emphasize how composition is much better for creating "simple" things that reuse complex things under-the-hood. So Matt, when the book comes out it will look like I've cribbed your ideas :-)

By on 10/22/2007 5:26 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper