Write to a log file asynchronously from asp.net task - asp.net

Using VB.Net (Framework version 4.5.1)
I have a program that sets up a list of (System.Threading.Tasks.Task) tasks that are executed as follows (only relevant code is shown):
po = New System.Threading.Tasks.ParallelOptions()
po.MaxDegreeOfParallelism = 5
Parallel.ForEach( task_list, po, AddressOf do_work )
The program works fine, but I want to add a log file rather than using just Console.WriteLine()
In the do_work() Sub, I want to write to a log file, for example:
Sub do_work( param As String )
Dim thread_id as String = "Thread ID " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() & ": "
joblog_append( thread_id & "Beg" )
joblog_append( thread_id & "param = " & param )
joblog_append( thread_id & "End" )
End Sub
Ideally, I would like to create three functions such as:
joblog_open() to open a log file at the start o the program.
joblog_append() to append to the log file; callable from within any task
joblog_close() to close the log file
What is the correct way to implement such logging when the joblog_append() will be called from multiple tasks being executed on separate threads?
All attempts I have tried so far seem to be hit and miss; sometimes the data is written to the output file, sometimes it is not.
Any advice (or better yet, a code example) would be most appreciated.
Thanks in advance.

I think that the issue you have is due to the fact multiple threads are accessing the file at the same time.
A better approach would be to make sure only one thread access the file at a time. You could use a lock (see this SO) or append the messages to be written to a concurrentQueue of string, then process that queue on another thread.
The joblog_append calls _Logger.Log, which in turns enqueue the message and start a new thread to process the queue.
private _Logger as new Logger
Private Sub joblog_append(Message As String)
_Logger.Log(Message)
End Sub
The logger class performs the following.
Append message to to a concurrent queue
Create and start a task (if no one already running) to write queue content to the file.
Set the task to nothing when completed
In the event the task is already created, the message is enqueued and the While condition in the task itself should take care of any messages added while it's running.
'Missing: Idisposable, Fileaccess.Shared,
'TODO: remove debugger.break
Public class Logger
Public Property Messages As new ConcurrentQueue(Of string)
private _WorkerTask as Task
private event WorkerTaskCompleted
private _Stream as FileStream
private _Writer as StreamWriter
Public sub New()
_Stream = io.file.OpenWrite("Mylog.txt")
_Writer = New StreamWriter(_Stream)
End sub
Public sub Log(Message as string)
Messages.Enqueue(Message)
if _WorkerTask Is Nothing
_WorkerTask = New Task(sub()
While Messages.Any
Dim CurrentMessage as string = ""
if Messages.TryDequeue(CurrentMessage)
_Writer.WriteLine(CurrentMessage)
else
debugger.Break
End If
End While
_Writer.Flush
_Stream.Flush
RaiseEvent WorkerTaskCompleted
End Sub)
_WorkerTask.Start
End If
End sub
Private Sub Logger_WorkerTaskCompleted() Handles Me.WorkerTaskCompleted
_WorkerTask = Nothing
End Sub
End Class
Please note. This is my approach to this problem but I do not have anything similar implemented and tested. Therefore, you will have to make your tests to confirm it is working properly.

Related

Is assigning an object to itself a good idea?

I have two classes, RecordSet and Record. RecordSet has a Generic List(Of Record).
I can add objects to the list by calling my RecordSet.AddRecord(ObjRecord) function, which returns RecordSet. When the list has a count of 200, some processing occurs and a new RecordSet object is returned, otherwise itself is returned and the application can carry on adding Record objects to the list.
My concern is that there will be 200 objects of RecordSet until garbage collection does it's sweep. Is this a good idea?
Public Class RecordSet
Private lstRecords As New List(Of Record)
Public Function AddRecord(SomeVariable) AS RecordSet
lstRecords.Add(New Record())
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
Return New RecordSet()
Else
Return Me
End If
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
End Sub
Private Class Record
Public Sub New()
End Sub
End Class
End Class
Then in my application I call:
Dim objRecordSet AS New RecordSet
For Each VariableName In SomeList
objRecordSet = objRecordSet.AddRecord(VariableName)
Next
'Process the remaining objects in objRecordSet here.
First of all, this is really bad pratice, it's hard to follow the code for someone new and is a potential bug source. Instead of returning urself every time, change your design.
Change your function to this:
Public Sub AddRecord(SomeVariable)
lstRecords.Add(New Record()) <--- should't you be doing something with SomeVariable?!
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
end if
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
Me.lstRecords.clear()
End Sub
Now AddRecord does exactly what it says it does - it adds a new record and modifies the recordSet. ProcessTheRecords does the processing, as its supposed to do, and if u need to clear the list container - oh well, just clear it.
I strongly recommed to read this wiki article about
Cohesion.
Just as a proposiontion, the AddRecord could be a function of return type Boolean, which indicates the success of the operation (maybe an error or exception can be raised by the processing function?).
It's much cleaner now, isn't it?

Instantiating a class within WCF

I'm writing a WCF WebMethod to upload files to, of which I taken snippets from around the web. The WCF interface looks like this:
<ServiceContract()>
Public Interface ITransferService
<OperationContract()>
Sub UploadFile(ByVal request As RemoteFileInfo)
End Interface
<MessageContract()>
Public Class RemoteFileInfo
Implements IDisposable
<MessageHeader(MustUnderstand:=True)>
Public FileName As String
<MessageHeader(MustUnderstand:=True)>
Public Length As Long
<MessageBodyMember(Order:=1)>
Public FileByteStream As System.IO.Stream
Public Sub Dispose() Implements IDisposable.Dispose
If FileByteStream IsNot Nothing Then
FileByteStream.Close()
FileByteStream = Nothing
End If
End Sub
End Class
Within ASP.NET, when the web method is consumed, for some reason it only works when the interface is used as part of the instantiation of RemoteFileInfo:
Protected Sub btn_Click(sender As Object, e As EventArgs) Handles btn.Click
If fu.HasFile Then
Dim fi As New System.IO.FileInfo(fu.PostedFile.FileName)
' this is the line in question --------------
Dim cu As ServiceReference1.ITransferService = New ServiceReference1.TransferServiceClient()
' -------------------------------------------
Dim uri As New ServiceReference1.RemoteFileInfo()
Using stream As New System.IO.FileStream(fu.PostedFile.FileName, IO.FileMode.Open, IO.FileAccess.Read)
uri.FileName = fu.FileName
uri.Length = fi.Length
uri.FileByteStream = stream
cu.UploadFile(uri)
End Using
End If
End Sub
Can anyone advise why it is not possible to create an instance of TransferService using the following approach:
Dim cu As New ServiceReference1.TransferServiceClient()
If I try the above, it breaks this line:
cu.UploadFile(uri)
...and UploadFile must be called with three parameters (FileName, Length, FileByteStream) even there is no method that uses this signature.
Why is the Interface required when creating an instance of this class please?
When you create the proxy for your service with the "Add Service Reference" dialog, by default the proxy creation code will "unwrap" message contracts, like the one you have. If you want the message contract to appear as you defined on the server side on your proxy, you need to select the "Advanced" tab, and check the "Always generate message contracts" option. With that you'll get the message contract in your client as well.
The issue is that when a MessageContract is encountered as a parameter, the WCF client generation assumes by default that you want to implement a messaging-style interface, and provides the discrete properties from the message contract as part of the client-side interface.
The Using Messaging Contracts article in MSDN contains a very detailed description of what can be done with a messaging contract and I suspect that Microsoft chose this default behavior because of some of the "games" that you can play with the messages.
However, if you examine the code generated for your UploadFile on the client side, there are some interesting tidbits that help to explain what is going on.
The first is the comments for the UploadFile method in the interface:
'CODEGEN: Generating message contract since the operation UploadFile is neither RPC nor document wrapped.
...
Function UploadFile(ByVal request As ServiceReference1.RemoteFileInfo) As ServiceReference1.UploadFileResponse
This implies that the contract would have been generated differently if the message contract had a different implementation.
The second is that you will see that there is nothing special about the code that is used to actually make the service call:
Public Sub UploadFile(ByVal FileName As String, ByVal Length As Long, ByVal FileByteStream As System.IO.Stream)
Dim inValue As ServiceReference1.RemoteFileInfo = New ServiceReference1.RemoteFileInfo()
inValue.FileName = FileName
inValue.Length = Length
inValue.FileByteStream = FileByteStream
Dim retVal As ServiceReference1.UploadFileResponse = CType(Me,ServiceReference1.ITransferService).UploadFile(inValue)
End Sub
So in this case, your code is doing exactly what the generated code does. However, if the MessageContract were more complex, I suspect that this would no longer be the case.
So, for your question:
Can anyone advise why it is not possible to create an instance of
TransferService using the following approach...
There is no reason not to take this approach as long as you verify that the implementation of the method call is functionality equivalent to your code.
There are a couple of options for changing the default generation of the method in the client:
1) Remove the MessageContract attribute from the RemoteFileInfo class.
2) Although it seems to be counter-intuitive, you can check the Always generate message contracts checkbox in the Configure Service Reference Dialog Box.

Same Function Running Parallel will override values?

I am Using same function from many places...
for example below function
Public Sub getUser(ByVal Name as string)
dim myName=Name
.......
insert(myName)
End Sub
I am using this function from so many places...
I have doubt should this function override this myName values with latest function call?
Suppose i called getUser("ABC") so value of myName is now ABC now sudden all call getUser("XYZ") so at insert(myName) will it insert("ABC") or insert("XYZ")??
I need it to be insert("ABC") and then insert("XYZ")
You can use locking to make sure only one thread does something at a time
//declare an object for locking
Dim lockObjcect As New [Object]()
Public Sub getUser(ByVal Name as string)
SyncLock lockObjcect
dim myName=Name
.......
insert(myName)
End SyncLock
End Sub
With the locking, now only one thread will be able to execute the code between SyncLock and End SyncLock this means First ABC will be inserted and then XYZ will be inserted

How to handle Console Application Close button event

I have a console application which communicating with WCF service on startup and after som computation we are entering some data in sql database. and in console app we are handling enter key to close the application and at the time of closing Application i am cleaning relevant data from Database. but It fails when some close the console application using mouse to click on close icon.(Instantly its close).
Please let me know that how to handle Close button event and also ctrl+f4.
The code is below:
Private m_ControllerID As String = String.Empty
Sub Main()
HostControllerService()
End Sub
Private Sub HostControllerService()
m_ControllerID = RegisterMe()
Console.WriteLine("Retention Controller Service is running...")
Console.WriteLine("Press <Enter> key to exit and UnRegister the service.")
Console.ReadLine()
UnRegisterMe(m_ControllerID)
End Sub
Private Function RegisterMe() As String
'Code snippet to Save data In database.
End Function
Private Sub UnRegisterMe(ByVal m_ControllerID As String)
'Cleaning the data from Database.
End Sub

Can't set Response object in ASP Classic

This line:
set Response = nothing
Fails with the error
"Microsoft VBScript runtime error '800a01b6'
Object doesn't support this property or method: 'Response' "
Now, I can think of any number of reasons why the engine might not want to let me do such a seemingly silly thing, but I'm not sure how a missing method could be stopping me.
EDIT: Here is an example of what I'd like to do with this.
class ResponseBufferEphemeron
private real_response_
private buffer_
private sub class_initialize
set real_response_ = Response
end sub
private sub class_terminate
set Response = real_response_
end sub
public function init (buf)
set buffer_ = buf
set init = me
end function
public function write (str)
buffer_.add str
end function
end class
function output_to (buf)
set output_to = (new ResponseBufferEphemeron).init(buf)
end function
dim buf: set buf = Str("Block output: ") ' My string class '
with output_to(buf)
Response.Write "Hello, World!"
end with
Response.Write buf ' => Block output: Hello, World! '
You can't set the Response to nothing.
The ASP Response object is used to send output to the user from the server.
What are you trying to do? If you're trying to end the Response to the user, use
Response.End
Well, I found the answer here: http://blogs.msdn.com/b/ericlippert/archive/2003/10/20/53248.aspx
So we special-cased VBScript so that it detects when it is compiling code that contains a call to Response.Write and there is a named item in the global namespace called Response that implements IResponse::Write. We generate an efficient early-bound call for this situation only.

Resources