Reading meta-data locks file - vb.net

I have the following function to read the recording date of a .jpg-file:
Public Shared Function GetRecordingDateOfPhoto(pathOfPhoto As String) As DateTime
If Not IO.File.Exists(pathOfPhoto) Then
Throw New FileNotFoundException
End If
Dim bitmapSource As BitmapSource = BitmapFrame.Create(New Uri(pathOfPhoto, UriKind.Relative))
Dim bitmapMetadata As BitmapMetadata = TryCast(bitmapSource.Metadata, BitmapMetadata)
Dim result As DateTime
If DateTime.TryParse(bitmapMetadata.DateTaken, result) Then
Return result
Else
Throw New FormatException
End If
End Function
This function returns the correct date, but when I do something like this
dim dateOfPhoto as Date = GetRecordingDateOfPhoto("foo.jpg")
My.Computer.FileSystem.MoveFile("foo.jpg", "bar.jpg")
then I get an exception from MoveFile(...): IOException ("The Process Cannot Access the File Because It Is Being Used by Another Process")
What do I have to change exactly (maybe using/end using?) in the GetRecordingDateOfPhoto(...)-function to avoid this exception?
Many thanks in advance.

Unfortunately, this class is just terribly implemented. Blame Microsoft. BitmapFrame.Create internally creates a stream and never closes it.
source: microsoft .net reference source

This is happening because the function is Shared. Its local variables exist only once regardless of how many class instantiations there are. Try making it not shared or set your variables to Nothing.

Related

How to set variable in second process from first process

first of all: I'm a newbie in this forum and not a professional programmer, just hobby.
This is what I have: One solution in VisualStudio with 3 Projects in VB.net.
First project contains common functions etc.
Second project is a windows service.
Third project is a Windows Forms UI.
Second an third project imports the first project.
My problem: When the service from second project is running and UI from third project is started I want to set a variable (e.g. by pressing a button) that will be set in the service, too. So the service is informed to do some special things.
I've tried to declare this variable in the common project, but this doesn't work. After searching a bit I know now this can't work, because the service and the UI are seperate processes.
There are several solutions to communicate between processes, e.g. IPC, shared memory, named pipes....
But isn't there a simple way to solve my problem ?
Thanks a lot and with best regards,
Matthias
OK, thanks for your answers!! :-)
I decided to try it with memory mapped file.
This is my making function:
Public Function MakeMemoryMappedFile(ByVal pValue As String) As String
Dim Buffer As Byte() = ASCIIEncoding.ASCII.GetBytes(pValue)
Try
Dim mmf As MemoryMappedFile = MemoryMappedFile.CreateOrOpen("test", 10000)
Dim accessor As MemoryMappedViewAccessor = mmf.CreateViewAccessor()
accessor.Write(54, CType(Buffer.Length, UShort))
accessor.WriteArray(54 + 2, Buffer, 0, Buffer.Length)
Return "ok"
Catch ex As Exception
Return "Error = " & ex.Message
End Try
End Function
And this is my reading function:
Public Function ReadMemoryMappedFile() As String
Try
Dim mmf As MemoryMappedFile = MemoryMappedFile.OpenExisting("test")
Dim accessor As MemoryMappedViewAccessor = mmf.CreateViewAccessor()
Dim Size As UShort = accessor.ReadUInt16(54)
Dim Buffer As Byte() = New Byte(Size - 1) {}
accessor.ReadArray(54 + 2, Buffer, 0, Buffer.Length)
Return ASCIIEncoding.ASCII.GetString(Buffer)
Catch noFile As FileNotFoundException
Return "No File found ..."
Catch ex As Exception
Return "Error = " & ex.Message
End Try
End Function
I have on my application a button for setting the value and one button for reading the value -> works fine.
If I start this application twice and set the value in the first instance and then read the value from the second instance -> it works fine.
But: If I set the value in the application my windows service can't read ist. Always got a "FileNotFoundException".
Could you please tell me whats wrong ?!?
Thanks !!!

ByRef Local Variable using Linq

I'm having trouble with selecting only part of a collection and passing it by reference.
So I have a custom class EntityCollection which is , who guessed, a collection of entities. I have to send these entities over HTTPSOAP to a webservice.
Sadly my collection is really big, let's say 10000000 entities, which throws me an HTTP error telling me that my request contains too much data.
The method I am sending it to takes a Reference of the collection so it can further complete the missing information that is autogenerated upon creation of an entity.
My initial solution:
For i As Integer = 0 To ecCreate.Count - 1 Step batchsize
Dim batch As EntityCollection = ecCreate.ToList().GetRange(i, Math.Min(batchsize, ecCreate.Count - i)).ToEntityCollection()
Q.Log.Write(SysEnums.LogLevelEnum.LogInformation, "SYNC KLA", "Creating " & String.Join(", ", batch.Select(Of String)(Function(e) e("nr_inca")).ToArray()))
Client.CreateMultiple(batch)
Next
ecCreate being an EntityCollection.
What I forgot was that using ToList() and ToEntityCollection() (which I wrote) it creates a new instance...
At least ToEntityCollection() does, idk about LINQ's ToList()...
<Extension()>
Public Function ToEntityCollection(ByVal source As IEnumerable(Of Entity)) As EntityCollection
Dim ec As New EntityCollection()
'ec.EntityTypeName = source.FirstOrDefault.EntityTypeName
For Each Entity In source
ec.Add(Entity)
Next
Return ec
End Function
Now, I don't imagine my problem would be solved if I change ByVal to ByRef in ToEntityCollection(), does it?
So how would I actually pass just a part of the collection byref to that function?
Thanks
EDIT after comments:
#Tim Schmelter it is for a nightly sync operation, having multiple selects on the database is more time intensive then storing the full dataset.
#Craig Are you saying that if i just leave it as an IEnumerable it will actually work? After all i call ToArray() in the createmultiple batch anyway so that wouldn't be too much of a problem to leave out...
#NetMage you're right i forgot to put in a key part of the code, here it is:
Public Class EntityCollection
Implements IList(Of Entity)
'...
Public Sub Add(item As Entity) Implements ICollection(Of Entity).Add
If IsNothing(EntityTypeName) Then
EntityTypeName = item.EntityTypeName
End If
If EntityTypeName IsNot Nothing AndAlso item.EntityTypeName IsNot Nothing AndAlso item.EntityTypeName <> EntityTypeName Then
Throw New Exception("EntityCollection can only be of one type!")
End If
Me.intList.Add(item)
End Sub
I Think that also explains the List thing... (BTW vb or c# don't matter i can do both :p)
BUT: You got me thinking properly:
Public Sub CreateMultiple(ByRef EntityCollection As EntityCollection)
'... do stuff to EC
Try
Dim ar = EntityCollection.ToArray()
Binding.CreateMultiple(ar) 'is also byref(webservice code)
EntityCollection.Collection = ar 'reset property, see below
Catch ex As SoapException
Raise(GetCurrentMethod(), ex)
End Try
End Sub
And the evil part( at least i think it is) :
Friend Property Collection As Object
Get
Return Me.intList
End Get
Set(value As Object)
Me.Clear()
For Each e As Object In value
Me.Add(New Entity(e))
Next
End Set
End Property
Now, i would still think this would work, since in my test if i don't use Linq or ToEntityCollection the byref stuff works perfectly fine. It is just when i do the batch thing, then it doesn't... I was guessing it could maybe have to do with me storing it in a local variable?
Thanks already for your time!
Anton
The problem was that i was replacing the references of Entity in my local batch, instead of in my big collection... I solved it by replacing the part of the collection that i sent as a batch with the batch itself, since ToList() and ToEntityCollection both create a new object with the same reference values...
Thanks for putting me in the correct direction guys!

Serialization between two applications

I have developed a Visual Basic.net application that uses serialization to save an object. I am wanting to open and save this object in two different Visual Basic.net applications.
What is the best way to do this? Do I need to create a class library to do this?
Can I please have some help for this?
EDIT
I am wanting to be able to open and save the object in both applications.
Depending on how complicated your data is, you should be able to simply mark your data's class with a <Serializable> attribute. You can then simply call the Serialize method in one application, save to disk, then read the file into your other application and call Deserialize.
You will need to define the class in both applications, which is easiest to do by sharing a common library.
See the MDSN example for basic serialization.
You can write/read to xml, then you would just have to check the folder where you save them from the other app to see if a new file has been created or updated.
Function to serialize object and write to xml
Public Function MyObjectFileGeneration()
Try
Dim strPath As String = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase)
strPath = Replace(strPath, "file:\", "")
Dim myObj = Form1.MyObject
Dim objStreamWriter As New StreamWriter(strPath & "\MyFolder\MyObj.xml", False)
Dim x As New XmlSerializer(s.GetType)
x.Serialize(objStreamWriter, MyObj)
objStreamWriter.Close()
Return True
Catch ex As Exception
'Do something here if failure...
Return False
End Try
End Function
Function to read xml and de-serialize to object
Public Function GetMyObjFromXMLFile() As MyObj
'check if file exists first...
If xmlFileExists() Then
Dim strPath As String = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase)
Dim objStreamReader As New StreamReader(Replace(strPath & "\MyFolder\MyObj.xml", "file:\", ""))
Dim MyObj As New MyObject
Dim x As New XmlSerializer(MyObj.GetType)
MyObj = x.Deserialize(objStreamReader)
objStreamReader.Close()
Return MyObj
Else
Return New MyObj
End If
End Function
I wish there was an easy way to do this, but unfortunately, I hit this wall also..
Serializable data can only be reread by the SAME application. (it gives you a lovely error message about this.) I tried using a serialized connection for simplified packet transfers, unsuccessfully..
Depending on how good you are at programming, I have a recommendation on this one..
Serialize your variables to a memorystream, then cut the header section out and shove it to another file stream, then when you reload it, save a variable to a memorystream to get the new header, then attach the remaining data, then read serialization..
haven't tried it yet, but when I get back to it, this is my next method.
I did see an XML method, but recommend using a compression/encryption library to keep your data safe.. did see some simple ways to possibly do that..
Sorry, I don't have code on this round, but when I investigate it, I will append this response..

Shared Resource in Parallel.ForEach

How do I control access to a shared resource in a Parallel.ForEach loop? I am trying to download multiple files in parallel, and I want to capture information about downloads that fail, so that the user can re-attempt the download later. However, I am worried that if more than one download fails at the same time, the application will throw an exception because one thread will attempt to access the file while it is being written to by another.
In the code below, I would like to know how to control access to the file at RepeateRequestPath. A RequestSet is a list of strings that represent IDs of the resource I am trying to download.
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
End Using
End Try
End Sub)
The usual way to protect a shared resource in VB.NET is to use SyncLock.
So, you would create a lock object before the Parallel.ForEach() loop:
Dim lock = New Object
and then you would use that inside the loop:
SyncLock lock
File.AppendAllLines(RepeatRequestPath, RequestSet)
End SyncLock
Also note that you can use AppendAllLines() even if the file doesn't exist yet, so you don't have to check for that.
You need to use a semaphore to control access to a shared resource. You want only one thread to access the error file at one time, so initialize the semaphore to only allow 1 thread in. Calling _pool.WaitOne should seize the semaphore, and then release it once it finishes creating/writing to the file.
Private Shared _pool As Semaphore
_pool = = New Semaphore(0, 1)
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
_pool.WaitOne()
Try
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
Catch ex as Exception
'Do some error handling here.
Finally
_pool.Release()
End Try
End Using
End Try
End Sub)
svick's solution is almost right. However if you need to protect access to a shared variable you also need to declare your lock object as shared at class level.
This works correctly:
Friend Class SomeClass
Private Shared _lock As New Object
Private Shared sharedInt As Integer = 0
Sub Main()
SyncLock _lock
sharedInt += 1
End SyncLock
End Sub
End Class
If you use a non-shared lock object, synclock will protect the variable only from multiple accessing threads within the same instance, not across instances.

VB.NET Checking if a File is Open before proceeding with a Read/Write?

Is there a method to verify that a file is open? The only thing I can think of is the Try/Catch to see if i can catch the file-open exception but I figured that a method be available to return true/false if file is open.
Currently using System.IO and the following code under class named Wallet.
Private holdPath As String = "defaultLog.txt"
Private _file As New FileStream(holdPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)
Private file As New StreamWriter(_file)
Public Function Check(ByVal CheckNumber As Integer, ByVal CheckAmount As Decimal) As Decimal
Try
file.WriteLine("testing")
file.Close()
Catch e As IOException
'Note sure if this is the proper way.
End Try
Return 0D
End Function
Any pointers will be appreciated! Thank you!!
Private Sub IsFileOpen(ByVal file As FileInfo)
Dim stream As FileStream = Nothing
Try
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
stream.Close()
Catch ex As Exception
If TypeOf ex Is IOException AndAlso IsFileLocked(ex) Then
' do something here, either close the file if you have a handle, show a msgbox, retry or as a last resort terminate the process - which could cause corruption and lose data
End If
End Try
End Sub
Private Shared Function IsFileLocked(exception As Exception) As Boolean
Dim errorCode As Integer = Marshal.GetHRForException(exception) And ((1 << 16) - 1)
Return errorCode = 32 OrElse errorCode = 33
End Function
Call it like this:
Call IsFileOpen(new FileInfo(filePath))
There is really no point using a 'is file in use check' function since you will still need to have try catch to handle the case that the file fails to open. The file open can fail for many more reasons than it just being already open.
Also using a function to do a check is no guarantee of success. The 'is file in use check' might return false only for the file open to fail with a file already open error, because in time between the check and trying to open the file it was opened by someone else.
It looks like the two suggestions from this MSDN forum posting both involve trying to open the file.
The first one is similar to what you are doing now, and the second involves using a Windows API function (CreateFile) and checking for a invalid handle signifying the file is in use. In both cases they are relying on an error condition to determine if the file is open or not. In short, in my opinion the method you are using is correct since there is not a System.IO.File.IsOpen property.