I've previously used the Task Parallel Library to keep the UI thread responsive, but I'm trying to switch to async/await to simplify the code. The project I'm working on is in WinForms. There is a presentation layer that communicates with view interfaces, and an assembly that contains winforms forms that implement these interfaces, with dependency injection binding everything together. In the specific example below, the form has a menu/button/etc that sends a request to the presentation layer for new data. I've ignored exception handling to keep the example simple.
In the form code we have an event handler like this:
private async sub DataRequest(sender as object, e as EventArgs) handles SomeButton.click
Dim etask = New Threading.Tasks.Task(Sub() RaiseEvent DataRefreshRequest(Me, EventArgs.Empty))
etask.Start()
Await etask
end sub
Not sure if there's a neater way of awaiting an event being raised, the above seems a bit clumsy
The presentation layer responds to this event with code like this:
Private Async Sub HandleDataRequest() Handles _view.DataRefreshRequest
Dim gdtask = New Tasks.Task(Of IEnumerable(Of Summary))(Function() GetDataTask())
gdtask.Start()
_view.Data = Await gdtask
End Sub
We are stuck with .Net 4 on this project so we are using Microsoft.BCL.Async. GetDataTask is effectively a Linq query on entities exposed by the business layer, but without .Net 4.5 we can't execute it using .ToListAsync
The bit I'm a little unsure of is the way I'm raising the event in the winforms assembly. Is that the right way to do it? It seems to work okay, but I'm concerned about any unforeseen problems of doing it this way. Better to do it right from the start then find there's a problem once this pattern is in common use.
Using Task.Run is better than the Task constructor with Task.Start.
Other than that, I'd just recommend some comments. As you noted, the ideal solution would be to use ToListAsync and make it truly asynchronous, but that's not possible given your platform.
Task.Run is normally used for running CPU-bound code on a background thread, and this code is not CPU-bound. So I'd recommend a comment for your future self so that when you do upgrade to .NET 4.5, you can know those Task.Run calls are no longer necessary.
Related
Can anyone explain why the following code works on some Win7 PC's but on some I get a MissingMethodException and the Timer.Elapsed event is never called.
Private Sub _timer2_Elapsed(ByVal sender As Object, ByVal e As System.EventArgs) Handles _timer2.Elapsed
_timer2.Enabled = False
Dispatcher.Invoke(Sub()
HandleSingleKeyPress(-1)
End Sub)
End Sub
After some investigation I have found that the following code works much better:-
Public Delegate Sub InvokedSubDelegate()
Private Sub _timer2_Elapsed(ByVal sender As Object, ByVal e As System.EventArgs) Handles _timer2.Elapsed
_timer2.Enabled = False
Dispatcher.Invoke(New InvokedSubDelegate(Sub()
HandleSingleKeyPress(-1)
End Sub))
End Sub
Not sure why the first approach works only sometimes but hope the solution can help someone else with similar problems.
Jerry
It doesn't sound like you are close to identifying the true problem. There certainly is more than one in that snippet.
A MissingMethodException is a DLL Hell problem. In other words, you are running your code with an old version of the assembly, one that doesn't yet have the method you are trying to call. You avoid DLL Hell by paying lots of attention when you deploy the assemblies. And by religiously incrementing the [AssemblyVersion]. In the VB.NET IDE that's done with Project + Properties, Application tab, Assembly Information button. This does explain why the 2nd snippet doesn't seem to have this problem, you are just less likely to be running with that old version of the assembly.
This does end up rather poorly when you use the System.Timers.Timer class. It is a nasty class. In a highly uncharacteristic lapse of judgement, Microsoft decided to swallow all exceptions that are raised in the Elapsed event handler. Which explains why the timer appears to be stop working, it just won't do what you asked it to do when the exception aborts the code. Favor the System.Threading.Timer class, it doesn't swallow exceptions. Or always using try/catch inside the Elapsed handler, albeit that it is pretty hard to figure out what to do when you catch. Environment.Exit() is wise.
But most of all, you are just using the entirely wrong timer. There's no point in using an asynchronous one when you make it synchronous again by using Dispatcher.Begin/Invoke(). Simply use a DispatcherTimer instead. Gets you the exact same outcome, minus the nastiness and overhead. And the need to ask this question.
We are trying to setup a SysTray application which can be activated from elsewhere. To be more specific the activation will come from a third party app which we cannot modify but allows us to activate our own app via its path (plus a parameter/argument).
When it gets activated we want to put up a BalloonText, there are to be no forms involved.
Therefore we have two problems to solve:
Make our SysTray application single instance (since it's no good generating multiple instances).
Allow this other app to activate our application with arguments
Lots of help out there to help learners create simple SysTray applications (and indeed we've done it ourselves as part of a solution to an unconnected project).
However we've never tried to make it single instance before.
Lots of help out there to help learners create single instance Winforms applications (again we've done this as part of other projects) but always simple applications with conventional forms (not SysTray). We use the VisualBasic WindowsFormsApplicationBase method.
Can't seem to combine these two approaches into a single solution.
Update:
Hans answer below nails it (and especially his comment):
This is already taken care of with a NotifyIcon, drop it on the form.
And the "Make single instance application" checkbox. And the
StartupNextInstance event. You'll need to stop assuming there's
anything special about this
As far as your first question about checking for other instances this seems to work. I used the CodeProject example as a baseline. In your Sub Main routine you can check for other instances by using the GetProcessesByName Method of the Process class. Something like this:
Public Sub Main()
'Turn visual styles back on
Application.EnableVisualStyles()
'Run the application using AppContext
Dim p() As Process
p = Process.GetProcessesByName("TrayApp") 'Your application name here
If UBound(p) >= 0 Then
End
End If
Application.Run(New AppContext)
End Sub
For the second question if your SysTray application is already running you might want to give this article on .Net Interprocess Communication a try. Otherwise parse the CommandLine arguments in yourSub Main as it is created.
From above article:
The XDMessaging library provides an easy-to-use, zero-configuration solution to same-box cross-AppDomain communications. It provides a simple API for sending and receiving targeted string messages across application boundaries. The library allows the use of user-defined pseudo 'channels' through which messages may be sent and received. Any application can send a message to any channel, but it must register as a listener with the channel in order to receive. In this way, developers can quickly and programmatically devise how best their applications can communicate with each other and work in harmony.
Everything becomes trivial when you actually do use a form. It is simple to put your app together with the designer, simple to get your app to terminate, simple to avoid a ghost icon in the tray, simple to create a context menu, simple to add popups if you ever need them.
The only un-simple thing is getting the form to not display. Paste this code in the form's class:
Protected Overrides Sub SetVisibleCore(ByVal value As Boolean)
If Not Me.IsHandleCreated Then
Me.CreateHandle()
value = False
End If
MyBase.SetVisibleCore(value)
End Sub
The "Exit" command in the context menu is now simply:
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
Me.Close()
End Sub
Presuming you have an email folder and messages in it. Each message has Body property, which needs to be loaded async and notify you when done, how would you approach this?
1 - Message.LoadBody() + event Message.BodyLoadComplete
2 - Message.LoadBody(Action completeDelegate)
For the sake of completeness WPF and Prism are involved.
Thanks!
Edit:
Message will be a UI entity which will wrap a IMessage interface (that is not UI Ready (no INPC)), so the reason i'm asking is that we have to settle for an interface between UI and business layer.. IMessage. (The Business layer will be using an Imap library that already has some async pattern, but we don't want to depend too much on any imp, thats why i'm trying to figure out the best interface ..
If you're using .NET 4, I'd use:
Task<string> LoadBodyAsync()
(You might want to implement this using TaskCompletionSource<TResult>.)
The caller can then add continuations etc however they wish... and most importantly, in the brave new world of .NET 4.5 and C# 5, this will work seamlessly with the async/await features.
Of your two options:
Message.LoadBody() // Plus an event, Message.BodyLoadComplete
// or ...
Message.LoadBody(Action completeDelegate)
The event option is more flexible and may sometimes be less painful to use. If you don't care when or if LoadBody completes, then you aren't forced to provide a fake callback. And you can bind the completion event to multiple event handlers, which might sometimes be useful (e.g. if multiple UI controls need to be updated).
Both your solutions are non-typical, though. The typical "old" way to do this is for LoadBody to be split into BeginLoadBody and EndLoadBody, and give the user an IAsyncResult. See this article. The typical "new" way to do it is outlined in Jon Skeet's answer.
If you're using Prism you should consider using the EventAggregator and publishing a message to indicate a mail has loaded. This will allow you to easily have multiple loosely-coupled subscribers for this "event".
Using the EventAggregator is an elegant solution for publishing events, and leads to a much cleaner and decoupled architecture which is easier to extend. For instance, if you wish to add a new feature to email loading such as progress indication, you can simply subscribe to the EmailLoaded message and you're done, you don't have to tightly couple your new component to emails via an event or callback, they do not need to know about each other.
I am struggling with detecting whether an entity in EF4 has changes that need saving.
After a few years away from .NET rusty is definitely the word I'd use to describe where I am right now. I am trying to learn to use EF4 and WPF while reacquainting myself with .NET. I've followed a number of tutorials on Drag & Drop Databinding with the Entity Framework and WPF and built an app with a few Windows that is getting my knowledge up little by little.
I am using the simplest part of my Model for my training exercises, The model has entities: Network and Laboratory, there is a many-to-many link between Networks and Labs, namely NetworkLabs, the relationship is not particularly important right now as I am still at the very basics.
I have a window that displays a list of Networks in a listbox, with a DataGrid next to it showing the Laboratories in the Network. I was able to do that fairly easily following the tutorials and I ended up with code like:
Public Class NetworkListWindow
Private Function GetNetworksQuery(entities As UKNEQASEntities) As ObjectQuery(Of Network)
Dim networksQuery As ObjectQuery(Of Network) = entities.Networks
' Update the query to include NetworkLabs data in Networks.
networksQuery = networksQuery.Include("NetworkLabs")
' Returns an ObjectQuery
Return networksQuery
End Function
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim entities As UKNEQASEntities = New UKNEQASEntities()
' Load data into Networks.
Dim networksViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim networksQuery As ObjectQuery(Of Network) = GetNetworksQuery(entities)
networksViewSource.Source = networksQuery.Execute(MergeOption.AppendOnly)
End Sub
End Class
That window is for viewing only, the user can click an edit button to edit the selected network. That second window is where I am hitting problems, on that window I dragged the network entity over from the Data Sources window to create a details screen (a grid with labels and textboxes in the rows and columns). I ended up with code like:
Public Class NetworkWindow
Private m_id As Integer
Private m_db As New UKNEQASEntities
Public Sub New(id As Integer)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
m_id = id
End Sub
Private Function GetNetworkQuery() As ObjectQuery(Of Network)
Dim networkQuery As ObjectQuery(Of Network) = m_db.Networks
' Update the query to include only the Network we are editing
networkQuery = networkQuery.Where(Function(net) net.Id = m_id)
' Update the query to include NetworkLabs data in Networks.
networkQuery = networkQuery.Include("NetworkLabs")
' Returns an ObjectQuery
Return networkQuery
End Function
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
' Load data into Networks.
Dim networkViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim networksQuery As ObjectQuery(Of Network) = GetNetworkQuery()
networkViewSource.Source = networksQuery.Execute(MergeOption.AppendOnly)
' Get laboratories that are not in any networks
Dim labResult = From laboratory In m_db.Laboratories _
Where _
Not _
(From networklab In m_db.NetworkLabs _
Select networklab.Laboratory.Id).Contains(laboratory.Id) _
Select laboratory
Dim laboratoriesViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesLaboratoriesViewSource"), CollectionViewSource)
laboratoriesViewSource.Source = labResult.ToList
End Sub
End Class
And that works fine for showing the network that was selected on the previous screen, I put a Save button on a toolbar which simply calls
m_db.SaveChanges()
To save the changes and that works fine as well. My problem comes when catering for when the user edits the data and closes the window, I want to detect whether the current network needs saving back to the database so I can prompt the user but I don't know how to get hold of the network to check.
I suspect it is something to do with code like:
Dim networkViewSource As CollectionViewSource = CType(Me.FindResource("UKNEQASEntitiesNetworksViewSource"), CollectionViewSource)
Dim entry As ObjectStateEntry = m_db.ObjectStateManager.GetObjectStateEntry(....)
but I don't know how to get the network to pass to GetObjectStateEntry.
On my previous list screen I was able to get the selected network by getting the SelectedItem from the listbox but I can't find anything that would help me on my single entry window.
Am I going about this the right way? For the single entry edit screen I am still using CollectionViewSource like I did for the list screen, is that the best way or is there something for single entities?
I have been looking for lots of tutorials and the majority I find are all about displaying the data for editing in DataGrids which is not what I am looking for. I am struggling to find any help on making screens for editing single entities so don't know how to pick up a reference to the entity the user is editing.
Any help is greatly appreciated as I am a novice at this EF and XAML lark.
You are looking at it from the wrong perspective. Several things that you need to consider when you start using EF:
You hardly need to look at
ObjectStateManager, this is something
EF is supposed to do for you. For example if you
want to close your Window and save
everything, just call
context.SaveChanges() and let EF to
the work of finding out what needs
saving and what not. There are cases where you need to use the ObjectStateManager, but these are specific and usually associated with very custom things.
You are not seeing many examples,
because people that are building WPF
and Silverlight applications aren't
connected to Data Base engines,
instead they connect to one or more
layers of Web Services and interact
with them. When you do this, the
first thing it happens is that you no
longer work with the "normal" EF
entities, these weren't made to be
serialized. You will work with either
POCO Entities of Self Tracking
Entities.
If you are using the normal Entities,
they are designed to allways work
attached to a context (in your case
it would be: UKNEQASEntities), this
means that if you attempt to use this
in a client application you will want
to make sure you allways use the same
context, so either have a reference
to it on a Singleton class or inject
it in a Dependency Injection
container. If you use multiple
contexts you will come across a bunch
of problems related with the
EntityKeys which are given to objects
in relation to the context they exist
in (consider the context a memory
copy of your Data Base, this isn't
true in practice, it works more like
an interface that you use to
auto-generate sql queries against
your Data Base.
In the end, if you want to build a good WPF client application you need to also use:
Dependency Injection container:
either MEF (which is deployed on
a .Net deployment) or Unity
(which is part of the Enterprise
Library).
EF4 Self Tracking Entities (I
personally perfer these over POCO
objects, because you get a lot of
powerfull functionality for free).
You can also poke me directly if you need more specific help, I've been working with this stuff for a while now, in professional development teams on medium to large scale projects. However I have never worked in Visual Basic, so I have a bit of pain when I have to read VB code!
I have started messing around with the MVVP pattern, and I am having some problems with UI responsiveness versus data processing.
I have a program that tracks packages. Shipment and package entities are persisted in SQL database, and are displayed in a WPF view. Upon initial retrieval of the records, there is a noticeable pause before displaying the new shipments view, and I have not even implemented the code that counts shipments that are overdue/active yet (which will necessitate a tracking check via web service, and a lot of time).
I have built this with the Ocean framework, and all appears to be doing well, except when I first started my foray into multi-threading. It broke, and it appeared to break something in Ocean... Here is what I did:
Private QueryThread As New System.Threading.Thread(AddressOf GetShipments)
Public Sub New()
' Insert code required on object creation below this point.
Me.New(ViewManagerService.CreateInstance, ViewModelUIService.CreateInstance)
'Perform initial query of shipments
'QueryThread.Start()
GetShipments()
Console.WriteLine(Me.Shipments.Count)
End Sub
Public Sub New(ByVal objIViewManagerService As IViewManagerService, ByVal objIViewModelUIService As IViewModelUIService)
MyBase.New(objIViewModelUIService)
End Sub
Public Sub GetShipments()
Dim InitialResults = From shipment In db.Shipment.Include("Packages") _
Select shipment
Me.Shipments = New ShipmentsCollection(InitialResults, db)
End Sub
So I declared a new Thread, assigned it the GetShipments method and instanced it in the default constructor. Ocean freaks out at this, so there must be a better way of doing it.
I have not had the chance to figure out the usage of the SQL ORM thing in Ocean so I am using Entity Framework (perhaps one of these days i will look at NHibernate or something too).
I have looked at a number of articles and they all have examples of simple uses. Some have mentioned the Dispatcher, but none really go very far into how it is used.
The way I do it is, using a backgroundworker. Then I use MVVM Light to send a message to the main ui to update progress.